1/*
2 * Copyright (C) 2011-2013 University of Washington. All rights reserved.
3 * Copyright (C) 2014 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
18 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "EventLoopInputDispatcher.h"
30
31#if ENABLE(WEB_REPLAY)
32
33#include "Page.h"
34#include "ReplayInputTypes.h"
35#include "ReplayingInputCursor.h"
36#include <wtf/TemporaryChange.h>
37
38#if !LOG_DISABLED
39#include "Logging.h"
40#include "SerializationMethods.h"
41#include <replay/EncodedValue.h>
42#include <wtf/text/CString.h>
43#endif
44
45namespace WebCore {
46
47EventLoopInputDispatcher::EventLoopInputDispatcher(Page& page, ReplayingInputCursor& cursor, EventLoopInputDispatcherClient* client)
48    : m_page(page)
49    , m_client(client)
50    , m_cursor(cursor)
51    , m_timer(this, &EventLoopInputDispatcher::timerFired)
52    , m_runningInput(nullptr)
53    , m_dispatching(false)
54    , m_running(false)
55    , m_speed(DispatchSpeed::FastForward)
56    , m_previousDispatchStartTime(0.0)
57    , m_previousInputTimestamp(0.0)
58{
59}
60
61void EventLoopInputDispatcher::run()
62{
63    ASSERT(!m_running);
64    m_running = true;
65
66    LOG(WebReplay, "%-20s Starting dispatch of event loop inputs for page: %p\n", "ReplayEvents", &m_page);
67    dispatchInputSoon();
68}
69
70void EventLoopInputDispatcher::pause()
71{
72    ASSERT(!m_dispatching);
73    ASSERT(m_running);
74    m_running = false;
75
76    LOG(WebReplay, "%-20s Pausing dispatch of event loop inputs for page: %p\n", "ReplayEvents", &m_page);
77    if (m_timer.isActive())
78        m_timer.stop();
79}
80
81void EventLoopInputDispatcher::timerFired(Timer<EventLoopInputDispatcher>*)
82{
83    dispatchInput();
84}
85
86void EventLoopInputDispatcher::dispatchInputSoon()
87{
88    ASSERT(m_running);
89
90    // We may already have an input if replay was paused just before dispatching.
91    if (!m_runningInput)
92        m_runningInput = static_cast<EventLoopInputBase*>(m_cursor.uncheckedLoadInput(InputQueue::EventLoopInput));
93
94    if (m_timer.isActive())
95        m_timer.stop();
96
97    double waitInterval = 0;
98
99    if (m_speed == DispatchSpeed::RealTime) {
100        // The goal is to reproduce the dispatch delay between inputs as it was
101        // was observed during the recording. So, we need to compute how much time
102        // to wait such that the elapsed time plus the wait time will equal the
103        // observed delay between the previous and current input.
104
105        if (!m_previousInputTimestamp)
106            m_previousInputTimestamp = m_runningInput->timestamp();
107
108        double targetInterval = m_runningInput->timestamp() - m_previousInputTimestamp;
109        double elapsed = monotonicallyIncreasingTime() - m_previousDispatchStartTime;
110        waitInterval = targetInterval - elapsed;
111    }
112
113    // A negative wait time means that dispatch took longer on replay than on
114    // capture. In this case, proceed without waiting at all.
115    if (waitInterval < 0)
116        waitInterval = 0;
117
118    if (waitInterval > 1000.0) {
119        LOG_ERROR("%-20s Tried to wait for over 1000 seconds before dispatching next event loop input; this is probably a bug.", "ReplayEvents");
120        waitInterval = 0;
121    }
122
123    LOG(WebReplay, "%-20s (WAIT: %.3f ms)", "ReplayEvents", waitInterval * 1000.0);
124    m_timer.startOneShot(waitInterval);
125}
126
127void EventLoopInputDispatcher::dispatchInput()
128{
129    ASSERT(m_runningInput);
130    ASSERT(!m_dispatching);
131
132    if (m_speed == DispatchSpeed::RealTime) {
133        m_previousDispatchStartTime = monotonicallyIncreasingTime();
134        m_previousInputTimestamp = m_runningInput->timestamp();
135    }
136
137#if !LOG_DISABLED
138    EncodedValue encodedInput = EncodingTraits<NondeterministicInputBase>::encodeValue(*m_runningInput);
139    String jsonString = encodedInput.asObject()->toJSONString();
140
141    LOG(WebReplay, "%-20s ----------------------------------------------", "ReplayEvents");
142    LOG(WebReplay, "%-20s >DISPATCH: %s %s\n", "ReplayEvents", m_runningInput->type().string().utf8().data(), jsonString.utf8().data());
143#endif
144
145    m_client->willDispatchInput(*m_runningInput);
146    // Client could stop replay in the previous callback, so check again.
147    if (!m_running)
148        return;
149
150    {
151        TemporaryChange<bool> change(m_dispatching, true);
152        m_runningInput->dispatch(m_page.replayController());
153    }
154
155    EventLoopInputBase* dispatchedInput = m_runningInput;
156    m_runningInput = nullptr;
157
158    // Notify clients that the event was dispatched.
159    m_client->didDispatchInput(*dispatchedInput);
160    if (dispatchedInput->type() == inputTypes().EndSegmentSentinel) {
161        m_running = false;
162        m_dispatching = false;
163        m_client->didDispatchFinalInput();
164        return;
165    }
166
167    // Clients could stop replay during event dispatch, or from any callback above.
168    if (!m_running)
169        return;
170
171    dispatchInputSoon();
172}
173
174} // namespace WebCore
175
176#endif // ENABLE(WEB_REPLAY)
177