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 "ReplayController.h"
30
31#if ENABLE(WEB_REPLAY)
32
33#include "AllReplayInputs.h"
34#include "CapturingInputCursor.h"
35#include "DOMWindow.h"
36#include "DocumentLoader.h"
37#include "Frame.h"
38#include "FrameTree.h"
39#include "InspectorInstrumentation.h"
40#include "Location.h"
41#include "Logging.h"
42#include "MainFrame.h"
43#include "Page.h"
44#include "ReplaySession.h"
45#include "ReplaySessionSegment.h"
46#include "ReplayingInputCursor.h"
47#include "ScriptController.h"
48#include "SerializationMethods.h"
49#include "Settings.h"
50#include "UserInputBridge.h"
51#include "WebReplayInputs.h"
52#include <replay/EmptyInputCursor.h>
53#include <wtf/text/CString.h>
54
55#if ENABLE(ASYNC_SCROLLING)
56#include "ScrollingCoordinator.h"
57#endif
58
59namespace WebCore {
60
61static void logDispatchedDOMEvent(const Event& event, bool eventIsUnrelated)
62{
63#if !LOG_DISABLED
64    EventTarget* target = event.target();
65    if (!target)
66        return;
67
68    // A DOM event is unrelated if it is being dispatched to a document that is neither capturing nor replaying.
69    if (Node* node = target->toNode()) {
70        LOG(WebReplay, "%-20s --->%s DOM event: type=%s, target=%lu/node[%p] %s\n", "ReplayEvents",
71            (eventIsUnrelated) ? "Unrelated" : "Dispatching",
72            event.type().string().utf8().data(),
73            frameIndexFromDocument((node->inDocument()) ? &node->document() : node->ownerDocument()),
74            node,
75            node->nodeName().utf8().data());
76    } else if (DOMWindow* window = target->toDOMWindow()) {
77        LOG(WebReplay, "%-20s --->%s DOM event: type=%s, target=%lu/window[%p] %s\n", "ReplayEvents",
78            (eventIsUnrelated) ? "Unrelated" : "Dispatching",
79            event.type().string().utf8().data(),
80            frameIndexFromDocument(window->document()),
81            window,
82            window->location()->href().utf8().data());
83    }
84#else
85    UNUSED_PARAM(event);
86    UNUSED_PARAM(eventIsUnrelated);
87#endif
88}
89
90ReplayController::ReplayController(Page& page)
91    : m_page(page)
92    , m_loadedSegment(nullptr)
93    , m_loadedSession(ReplaySession::create())
94    , m_emptyCursor(EmptyInputCursor::create())
95    , m_activeCursor(nullptr)
96    , m_targetPosition(ReplayPosition(0, 0))
97    , m_currentPosition(ReplayPosition(0, 0))
98    , m_segmentState(SegmentState::Unloaded)
99    , m_sessionState(SessionState::Inactive)
100    , m_dispatchSpeed(DispatchSpeed::FastForward)
101{
102}
103
104void ReplayController::setForceDeterministicSettings(bool shouldForceDeterministicBehavior)
105{
106    ASSERT(shouldForceDeterministicBehavior ^ (m_sessionState == SessionState::Inactive));
107
108    if (shouldForceDeterministicBehavior) {
109        m_savedSettings.usesPageCache = m_page.settings().usesPageCache();
110
111        m_page.settings().setUsesPageCache(false);
112    } else {
113        m_page.settings().setUsesPageCache(m_savedSettings.usesPageCache);
114    }
115
116#if ENABLE(ASYNC_SCROLLING)
117    if (ScrollingCoordinator* scrollingCoordinator = m_page.scrollingCoordinator())
118        scrollingCoordinator->replaySessionStateDidChange();
119#endif
120}
121
122void ReplayController::setSessionState(SessionState state)
123{
124    ASSERT(state != m_sessionState);
125
126    switch (m_sessionState) {
127    case SessionState::Capturing:
128        ASSERT(state == SessionState::Inactive);
129
130        m_sessionState = state;
131        m_page.userInputBridge().setState(UserInputBridge::State::Open);
132        break;
133
134    case SessionState::Inactive:
135        m_sessionState = state;
136        m_page.userInputBridge().setState(state == SessionState::Capturing ? UserInputBridge::State::Capturing : UserInputBridge::State::Replaying);
137        break;
138
139    case SessionState::Replaying:
140        ASSERT(state == SessionState::Inactive);
141
142        m_sessionState = state;
143        m_page.userInputBridge().setState(UserInputBridge::State::Open);
144        break;
145    }
146}
147
148void ReplayController::switchSession(PassRefPtr<ReplaySession> session)
149{
150    ASSERT(m_segmentState == SegmentState::Unloaded);
151    ASSERT(m_sessionState == SessionState::Inactive);
152
153    m_loadedSession = session;
154    m_currentPosition = ReplayPosition(0, 0);
155
156    LOG(WebReplay, "%-20sSwitching sessions from %p to %p.\n", "ReplayController", m_loadedSession.get(), session.get());
157    InspectorInstrumentation::sessionLoaded(&m_page, m_loadedSession);
158}
159
160void ReplayController::createSegment()
161{
162    ASSERT(m_sessionState == SessionState::Capturing);
163    ASSERT(m_segmentState == SegmentState::Unloaded);
164
165    m_segmentState = SegmentState::Appending;
166
167    // Create a new segment but don't associate it with the current session
168    // until we stop appending to it. This preserves the invariant that
169    // segments associated with a replay session have immutable data.
170    m_loadedSegment = ReplaySessionSegment::create();
171
172    LOG(WebReplay, "%-20s Created segment: %p.\n", "ReplayController", m_loadedSegment.get());
173    InspectorInstrumentation::segmentCreated(&m_page, m_loadedSegment);
174
175    m_activeCursor = m_loadedSegment->createCapturingCursor(m_page);
176    m_activeCursor->appendInput<BeginSegmentSentinel>();
177
178    std::unique_ptr<InitialNavigation> navigationInput = InitialNavigation::createFromPage(m_page);
179    // Dispatching this input schedules navigation of the main frame, causing a refresh.
180    navigationInput->dispatch(*this);
181    m_activeCursor->storeInput(WTF::move(navigationInput));
182}
183
184void ReplayController::completeSegment()
185{
186    ASSERT(m_sessionState == SessionState::Capturing);
187    ASSERT(m_segmentState == SegmentState::Appending);
188
189    m_activeCursor->appendInput<EndSegmentSentinel>();
190
191    // Hold on to a reference so unloading the segment doesn't deallocate it.
192    RefPtr<ReplaySessionSegment> segment = m_loadedSegment;
193    m_segmentState = SegmentState::Loaded;
194    bool shouldSuppressNotifications = true;
195    unloadSegment(shouldSuppressNotifications);
196
197    LOG(WebReplay, "%-20s Completed segment: %p.\n", "ReplayController", segment.get());
198    InspectorInstrumentation::segmentCompleted(&m_page, segment);
199
200    m_loadedSession->appendSegment(segment);
201    InspectorInstrumentation::sessionModified(&m_page, m_loadedSession);
202}
203
204void ReplayController::loadSegmentAtIndex(size_t segmentIndex)
205{
206    ASSERT(segmentIndex < m_loadedSession->size());
207    RefPtr<ReplaySessionSegment> segment = m_loadedSession->at(segmentIndex);
208
209    ASSERT(m_sessionState == SessionState::Replaying);
210    ASSERT(m_segmentState == SegmentState::Unloaded);
211    ASSERT(segment);
212    ASSERT(!m_loadedSegment);
213
214    m_loadedSegment = segment;
215    m_segmentState = SegmentState::Loaded;
216
217    m_currentPosition.segmentOffset = segmentIndex;
218    m_currentPosition.inputOffset = 0;
219
220    m_activeCursor = m_loadedSegment->createReplayingCursor(m_page, this);
221
222    LOG(WebReplay, "%-20sLoading segment: %p.\n", "ReplayController", segment.get());
223    InspectorInstrumentation::segmentLoaded(&m_page, segment);
224}
225
226void ReplayController::unloadSegment(bool suppressNotifications)
227{
228    ASSERT(m_sessionState != SessionState::Inactive);
229    ASSERT(m_segmentState == SegmentState::Loaded);
230
231    m_segmentState = SegmentState::Unloaded;
232
233    LOG(WebReplay, "%-20s Clearing input cursors for page: %p\n", "ReplayController", &m_page);
234
235    m_activeCursor = nullptr;
236    RefPtr<ReplaySessionSegment> unloadedSegment = m_loadedSegment.release();
237    for (Frame* frame = &m_page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
238        frame->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_emptyCursor);
239        frame->document()->setInputCursor(m_emptyCursor);
240    }
241
242    // When we stop capturing, don't send out segment unloaded events since we
243    // didn't send out the corresponding segmentLoaded event at the start of capture.
244    if (!suppressNotifications) {
245        LOG(WebReplay, "%-20sUnloading segment: %p.\n", "ReplayController", unloadedSegment.get());
246        InspectorInstrumentation::segmentUnloaded(&m_page);
247    }
248}
249
250void ReplayController::startCapturing()
251{
252    ASSERT(m_sessionState == SessionState::Inactive);
253    ASSERT(m_segmentState == SegmentState::Unloaded);
254
255    setSessionState(SessionState::Capturing);
256    setForceDeterministicSettings(true);
257
258    LOG(WebReplay, "%-20s Starting capture.\n", "ReplayController");
259    InspectorInstrumentation::captureStarted(&m_page);
260
261    m_currentPosition = ReplayPosition(0, 0);
262
263    createSegment();
264}
265
266void ReplayController::stopCapturing()
267{
268    ASSERT(m_sessionState == SessionState::Capturing);
269    ASSERT(m_segmentState == SegmentState::Appending);
270
271    completeSegment();
272
273    setSessionState(SessionState::Inactive);
274    setForceDeterministicSettings(false);
275
276    LOG(WebReplay, "%-20s Stopping capture.\n", "ReplayController");
277    InspectorInstrumentation::captureStopped(&m_page);
278}
279
280void ReplayController::startPlayback()
281{
282    ASSERT(m_sessionState == SessionState::Replaying);
283    ASSERT(m_segmentState == SegmentState::Loaded);
284
285    m_segmentState = SegmentState::Dispatching;
286
287    LOG(WebReplay, "%-20s Starting playback to position (segment: %d, input: %d).\n", "ReplayController", m_targetPosition.segmentOffset, m_targetPosition.inputOffset);
288    InspectorInstrumentation::playbackStarted(&m_page);
289
290    dispatcher().setDispatchSpeed(m_dispatchSpeed);
291    dispatcher().run();
292}
293
294void ReplayController::pausePlayback()
295{
296    ASSERT(m_sessionState == SessionState::Replaying);
297    ASSERT(m_segmentState == SegmentState::Dispatching);
298
299    if (dispatcher().isRunning())
300        dispatcher().pause();
301
302    m_segmentState = SegmentState::Loaded;
303
304    LOG(WebReplay, "%-20s Pausing playback at position (segment: %d, input: %d).\n", "ReplayController", m_currentPosition.segmentOffset, m_currentPosition.inputOffset);
305    InspectorInstrumentation::playbackPaused(&m_page, m_currentPosition);
306}
307
308void ReplayController::cancelPlayback()
309{
310    ASSERT(m_sessionState == SessionState::Replaying);
311    ASSERT(m_segmentState != SegmentState::Appending);
312
313    if (m_segmentState == SegmentState::Unloaded)
314        return;
315
316    if (m_segmentState == SegmentState::Dispatching)
317        pausePlayback();
318
319    ASSERT(m_segmentState == SegmentState::Loaded);
320    unloadSegment();
321    m_sessionState = SessionState::Inactive;
322    setForceDeterministicSettings(false);
323    InspectorInstrumentation::playbackFinished(&m_page);
324}
325
326void ReplayController::replayToPosition(const ReplayPosition& position, DispatchSpeed speed)
327{
328    ASSERT(m_sessionState != SessionState::Capturing);
329    ASSERT(m_segmentState == SegmentState::Loaded || m_segmentState == SegmentState::Unloaded);
330    ASSERT(position.segmentOffset < m_loadedSession->size());
331
332    m_dispatchSpeed = speed;
333
334    if (m_sessionState != SessionState::Replaying) {
335        setSessionState(SessionState::Replaying);
336        setForceDeterministicSettings(true);
337    }
338
339    if (m_segmentState == SegmentState::Unloaded)
340        loadSegmentAtIndex(position.segmentOffset);
341    else if (position.segmentOffset != m_currentPosition.segmentOffset || m_currentPosition.inputOffset > position.inputOffset) {
342        // If the desired segment is not loaded or we have gone past the desired input
343        // offset, then unload the current segment and load the appropriate segment.
344        unloadSegment();
345        loadSegmentAtIndex(position.segmentOffset);
346    }
347
348    ASSERT(m_currentPosition.segmentOffset == position.segmentOffset);
349    ASSERT(m_loadedSession->at(position.segmentOffset) == m_loadedSegment);
350
351    m_targetPosition = position;
352    startPlayback();
353}
354
355void ReplayController::frameNavigated(DocumentLoader* loader)
356{
357    ASSERT(m_sessionState != SessionState::Inactive);
358
359    // The initial capturing segment is created prior to main frame navigation.
360    // Otherwise, the prior capturing segment was completed when the frame detached,
361    // and it is now time to create a new segment.
362    if (m_sessionState == SessionState::Capturing && m_segmentState == SegmentState::Unloaded) {
363        m_currentPosition = ReplayPosition(m_currentPosition.segmentOffset + 1, 0);
364        createSegment();
365    }
366
367    // During playback, the next segment is loaded when the final input is dispatched,
368    // so nothing needs to be done here.
369
370    // We store the input cursor in both Document and JSDOMWindow, so that
371    // replay state is accessible from JavaScriptCore and script-free layout code.
372    loader->frame()->document()->setInputCursor(m_activeCursor.get());
373    loader->frame()->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_activeCursor.get());
374}
375
376void ReplayController::frameDetached(Frame* frame)
377{
378    ASSERT(m_sessionState != SessionState::Inactive);
379    ASSERT(frame);
380
381    if (!frame->document())
382        return;
383
384    // If the frame's cursor isn't capturing or replaying, we should do nothing.
385    // This is the case for the "outbound" frame when starting capture, or when
386    // we clear the input cursor to finish or prematurely unload a segment.
387    if (frame->document()->inputCursor().isCapturing()) {
388        ASSERT(m_segmentState == SegmentState::Appending);
389        completeSegment();
390    }
391
392    // During playback, the segments are unloaded and loaded when the final
393    // input has been dispatched. So, nothing needs to be done here.
394}
395
396void ReplayController::willDispatchEvent(const Event& event, Frame* frame)
397{
398    EventTarget* target = event.target();
399    if (!target && !frame)
400        return;
401
402    Document* document = frame ? frame->document() : nullptr;
403    // Fetch the document from the event target, because the target could be detached.
404    if (Node* node = target->toNode())
405        document = node->inDocument() ? &node->document() : node->ownerDocument();
406    else if (DOMWindow* window = target->toDOMWindow())
407        document = window->document();
408
409    ASSERT(document);
410
411    InputCursor& cursor = document->inputCursor();
412    bool eventIsUnrelated = !cursor.isCapturing() && !cursor.isReplaying();
413    logDispatchedDOMEvent(event, eventIsUnrelated);
414
415#if ENABLE_AGGRESSIVE_DETERMINISM_CHECKS
416    // To ensure deterministic JS execution, all DOM events must be dispatched deterministically.
417    // If these assertions fail, then this DOM event is being dispatched by a nondeterministic EventLoop
418    // cycle, and may cause program execution to diverge if any JS code runs because of the DOM event.
419    if (cursor.isCapturing())
420        ASSERT(static_cast<CapturingInputCursor&>(cursor).withinEventLoopInputExtent());
421    else if (cursor.isReplaying())
422        ASSERT(dispatcher().isDispatching());
423#endif
424}
425
426PassRefPtr<ReplaySession> ReplayController::loadedSession() const
427{
428    return m_loadedSession;
429}
430
431PassRefPtr<ReplaySessionSegment> ReplayController::loadedSegment() const
432{
433    return m_loadedSegment;
434}
435
436InputCursor& ReplayController::activeInputCursor() const
437{
438    return m_activeCursor ? *m_activeCursor : *m_emptyCursor;
439}
440
441EventLoopInputDispatcher& ReplayController::dispatcher() const
442{
443    ASSERT(m_sessionState == SessionState::Replaying);
444    ASSERT(m_segmentState == SegmentState::Dispatching);
445    ASSERT(m_activeCursor);
446    ASSERT(m_activeCursor->isReplaying());
447
448    return static_cast<ReplayingInputCursor&>(*m_activeCursor).dispatcher();
449}
450
451void ReplayController::willDispatchInput(const EventLoopInputBase&)
452{
453    ASSERT(m_sessionState == SessionState::Replaying);
454    ASSERT(m_segmentState == SegmentState::Dispatching);
455
456    m_currentPosition.inputOffset++;
457    if (m_currentPosition == m_targetPosition)
458        pausePlayback();
459}
460
461void ReplayController::didDispatchInput(const EventLoopInputBase&)
462{
463    ASSERT(m_sessionState == SessionState::Replaying);
464    ASSERT(m_segmentState == SegmentState::Dispatching);
465
466    InspectorInstrumentation::playbackHitPosition(&m_page, m_currentPosition);
467}
468
469void ReplayController::didDispatchFinalInput()
470{
471    ASSERT(m_segmentState == SegmentState::Dispatching);
472
473    // No more segments left to replay; stop.
474    if (m_currentPosition.segmentOffset + 1 == m_loadedSession->size()) {
475        // Normally the position is adjusted when loading the next segment.
476        m_currentPosition.segmentOffset++;
477        m_currentPosition.inputOffset = 0;
478
479        cancelPlayback();
480        return;
481    }
482
483    unloadSegment();
484    loadSegmentAtIndex(m_currentPosition.segmentOffset + 1);
485    startPlayback();
486}
487
488} // namespace WebCore
489
490#endif // ENABLE(WEB_REPLAY)
491