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