2 * Copyright (C) 2011-2013 University of Washington. All rights reserved.
3 * Copyright (C) 2014 Apple Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
29 #include "ReplayController.h"
31 #if ENABLE(WEB_REPLAY)
33 #include "AllReplayInputs.h"
34 #include "CapturingInputCursor.h"
35 #include "DOMWindow.h"
36 #include "DocumentLoader.h"
38 #include "FrameTree.h"
39 #include "InspectorInstrumentation.h"
42 #include "MainFrame.h"
44 #include "ReplaySession.h"
45 #include "ReplaySessionSegment.h"
46 #include "ReplayingInputCursor.h"
47 #include "ScriptController.h"
48 #include "SerializationMethods.h"
50 #include "UserInputBridge.h"
51 #include "WebReplayInputs.h"
52 #include <replay/EmptyInputCursor.h>
53 #include <wtf/text/CString.h>
55 #if ENABLE(ASYNC_SCROLLING)
56 #include "ScrollingCoordinator.h"
62 static void logDispatchedDOMEvent(const Event& event, bool eventIsUnrelated)
64 EventTarget* target = event.target();
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()),
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()),
82 window->location()->href().utf8().data());
86 static const char* sessionStateToString(SessionState state)
89 case SessionState::Capturing:
91 case SessionState::Inactive:
93 case SessionState::Replaying:
98 static const char* segmentStateToString(SegmentState state)
101 case SegmentState::Appending:
103 case SegmentState::Unloaded:
105 case SegmentState::Loaded:
107 case SegmentState::Dispatching:
108 return "Dispatching";
112 #endif // !LOG_DISABLED
114 ReplayController::ReplayController(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)
128 void ReplayController::setForceDeterministicSettings(bool shouldForceDeterministicBehavior)
130 ASSERT(shouldForceDeterministicBehavior ^ (m_sessionState == SessionState::Inactive));
132 if (shouldForceDeterministicBehavior) {
133 m_savedSettings.usesPageCache = m_page.settings().usesPageCache();
135 m_page.settings().setUsesPageCache(false);
137 m_page.settings().setUsesPageCache(m_savedSettings.usesPageCache);
140 #if ENABLE(ASYNC_SCROLLING)
141 if (ScrollingCoordinator* scrollingCoordinator = m_page.scrollingCoordinator())
142 scrollingCoordinator->replaySessionStateDidChange();
146 void ReplayController::setSessionState(SessionState state)
148 ASSERT(state != m_sessionState);
150 LOG(WebReplay, "%-20s SessionState transition: %10s --> %10s.\n", "ReplayController", sessionStateToString(m_sessionState), sessionStateToString(state));
152 switch (m_sessionState) {
153 case SessionState::Capturing:
154 ASSERT(state == SessionState::Inactive);
156 m_sessionState = state;
157 m_page.userInputBridge().setState(UserInputBridge::State::Open);
160 case SessionState::Inactive:
161 m_sessionState = state;
162 m_page.userInputBridge().setState(state == SessionState::Capturing ? UserInputBridge::State::Capturing : UserInputBridge::State::Replaying);
165 case SessionState::Replaying:
166 ASSERT(state == SessionState::Inactive);
168 m_sessionState = state;
169 m_page.userInputBridge().setState(UserInputBridge::State::Open);
174 void ReplayController::setSegmentState(SegmentState state)
176 ASSERT(state != m_segmentState);
178 LOG(WebReplay, "%-20s SegmentState transition: %10s --> %10s.\n", "ReplayController", segmentStateToString(m_segmentState), segmentStateToString(state));
180 switch (m_segmentState) {
181 case SegmentState::Appending:
182 ASSERT(state == SegmentState::Unloaded);
185 case SegmentState::Unloaded:
186 ASSERT(state == SegmentState::Appending || state == SegmentState::Loaded);
189 case SegmentState::Loaded:
190 ASSERT(state == SegmentState::Unloaded || state == SegmentState::Dispatching);
193 case SegmentState::Dispatching:
194 ASSERT(state == SegmentState::Loaded);
198 m_segmentState = state;
201 void ReplayController::switchSession(PassRefPtr<ReplaySession> session)
203 ASSERT(m_segmentState == SegmentState::Unloaded);
204 ASSERT(m_sessionState == SessionState::Inactive);
206 m_loadedSession = session;
207 m_currentPosition = ReplayPosition(0, 0);
209 LOG(WebReplay, "%-20sSwitching sessions from %p to %p.\n", "ReplayController", m_loadedSession.get(), session.get());
210 InspectorInstrumentation::sessionLoaded(&m_page, m_loadedSession);
213 void ReplayController::createSegment()
215 ASSERT(m_sessionState == SessionState::Capturing);
216 ASSERT(m_segmentState == SegmentState::Unloaded);
218 setSegmentState(SegmentState::Appending);
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();
225 LOG(WebReplay, "%-20s Created segment: %p.\n", "ReplayController", m_loadedSegment.get());
226 InspectorInstrumentation::segmentCreated(&m_page, m_loadedSegment);
228 m_activeCursor = CapturingInputCursor::create(m_loadedSegment);
229 m_activeCursor->appendInput<BeginSegmentSentinel>();
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));
237 void ReplayController::completeSegment()
239 ASSERT(m_sessionState == SessionState::Capturing);
240 ASSERT(m_segmentState == SegmentState::Appending);
242 m_activeCursor->appendInput<EndSegmentSentinel>();
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);
249 LOG(WebReplay, "%-20s Completed segment: %p.\n", "ReplayController", segment.get());
250 InspectorInstrumentation::segmentCompleted(&m_page, segment);
252 m_loadedSession->appendSegment(segment);
253 InspectorInstrumentation::sessionModified(&m_page, m_loadedSession);
256 void ReplayController::loadSegmentAtIndex(size_t segmentIndex)
258 ASSERT(segmentIndex < m_loadedSession->size());
259 RefPtr<ReplaySessionSegment> segment = m_loadedSession->at(segmentIndex);
261 ASSERT(m_sessionState == SessionState::Replaying);
262 ASSERT(m_segmentState == SegmentState::Unloaded);
264 ASSERT(!m_loadedSegment);
266 m_loadedSegment = segment;
267 setSegmentState(SegmentState::Loaded);
269 m_currentPosition.segmentOffset = segmentIndex;
270 m_currentPosition.inputOffset = 0;
272 m_activeCursor = ReplayingInputCursor::create(m_loadedSegment, m_page, this);
274 LOG(WebReplay, "%-20sLoading segment: %p.\n", "ReplayController", segment.get());
275 InspectorInstrumentation::segmentLoaded(&m_page, segment);
278 void ReplayController::unloadSegment(bool suppressNotifications)
280 ASSERT(m_sessionState != SessionState::Inactive);
281 ASSERT(m_segmentState == SegmentState::Loaded || m_segmentState == SegmentState::Appending);
283 setSegmentState(SegmentState::Unloaded);
285 LOG(WebReplay, "%-20s Clearing input cursors for page: %p\n", "ReplayController", &m_page);
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);
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);
302 void ReplayController::startCapturing()
304 ASSERT(m_sessionState == SessionState::Inactive);
305 ASSERT(m_segmentState == SegmentState::Unloaded);
307 setSessionState(SessionState::Capturing);
308 setForceDeterministicSettings(true);
310 LOG(WebReplay, "%-20s Starting capture.\n", "ReplayController");
311 InspectorInstrumentation::captureStarted(&m_page);
313 m_currentPosition = ReplayPosition(0, 0);
318 void ReplayController::stopCapturing()
320 ASSERT(m_sessionState == SessionState::Capturing);
321 ASSERT(m_segmentState == SegmentState::Appending);
325 setSessionState(SessionState::Inactive);
326 setForceDeterministicSettings(false);
328 LOG(WebReplay, "%-20s Stopping capture.\n", "ReplayController");
329 InspectorInstrumentation::captureStopped(&m_page);
332 void ReplayController::startPlayback()
334 ASSERT(m_sessionState == SessionState::Replaying);
335 ASSERT(m_segmentState == SegmentState::Loaded);
337 setSegmentState(SegmentState::Dispatching);
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);
342 dispatcher().setDispatchSpeed(m_dispatchSpeed);
346 void ReplayController::pausePlayback()
348 ASSERT(m_sessionState == SessionState::Replaying);
349 ASSERT(m_segmentState == SegmentState::Dispatching);
351 if (dispatcher().isRunning())
352 dispatcher().pause();
354 setSegmentState(SegmentState::Loaded);
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);
360 void ReplayController::cancelPlayback()
362 ASSERT(m_sessionState == SessionState::Replaying);
363 ASSERT(m_segmentState != SegmentState::Appending);
365 if (m_segmentState == SegmentState::Unloaded)
368 if (m_segmentState == SegmentState::Dispatching)
371 ASSERT(m_segmentState == SegmentState::Loaded);
373 m_sessionState = SessionState::Inactive;
374 setForceDeterministicSettings(false);
375 InspectorInstrumentation::playbackFinished(&m_page);
378 void ReplayController::replayToPosition(const ReplayPosition& position, DispatchSpeed speed)
380 ASSERT(m_sessionState != SessionState::Capturing);
381 ASSERT(m_segmentState == SegmentState::Loaded || m_segmentState == SegmentState::Unloaded);
382 ASSERT(position.segmentOffset < m_loadedSession->size());
384 m_dispatchSpeed = speed;
386 if (m_sessionState != SessionState::Replaying) {
387 setSessionState(SessionState::Replaying);
388 setForceDeterministicSettings(true);
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.
397 loadSegmentAtIndex(position.segmentOffset);
400 ASSERT(m_currentPosition.segmentOffset == position.segmentOffset);
401 ASSERT(m_loadedSession->at(position.segmentOffset) == m_loadedSegment);
403 m_targetPosition = position;
407 void ReplayController::frameNavigated(DocumentLoader* loader)
409 ASSERT(m_sessionState != SessionState::Inactive);
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);
419 // During playback, the next segment is loaded when the final input is dispatched,
420 // so nothing needs to be done here.
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());
428 void ReplayController::frameDetached(Frame* frame)
430 ASSERT(m_sessionState != SessionState::Inactive);
433 if (!frame->document())
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);
444 // During playback, the segments are unloaded and loaded when the final
445 // input has been dispatched. So, nothing needs to be done here.
448 void ReplayController::willDispatchEvent(const Event& event, Frame* frame)
450 EventTarget* target = event.target();
451 if (!target && !frame)
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();
462 InputCursor& cursor = document->inputCursor();
465 bool eventIsUnrelated = !cursor.isCapturing() && !cursor.isReplaying();
466 logDispatchedDOMEvent(event, eventIsUnrelated);
468 UNUSED_PARAM(cursor);
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());
482 PassRefPtr<ReplaySession> ReplayController::loadedSession() const
484 return m_loadedSession;
487 PassRefPtr<ReplaySessionSegment> ReplayController::loadedSegment() const
489 return m_loadedSegment;
492 InputCursor& ReplayController::activeInputCursor() const
494 return m_activeCursor ? *m_activeCursor : *m_emptyCursor;
497 EventLoopInputDispatcher& ReplayController::dispatcher() const
499 ASSERT(m_sessionState == SessionState::Replaying);
500 ASSERT(m_segmentState == SegmentState::Dispatching);
501 ASSERT(m_activeCursor);
502 ASSERT(m_activeCursor->isReplaying());
504 return static_cast<ReplayingInputCursor&>(*m_activeCursor).dispatcher();
507 void ReplayController::willDispatchInput(const EventLoopInputBase&)
509 ASSERT(m_sessionState == SessionState::Replaying);
510 ASSERT(m_segmentState == SegmentState::Dispatching);
512 m_currentPosition.inputOffset++;
513 if (m_currentPosition == m_targetPosition)
517 void ReplayController::didDispatchInput(const EventLoopInputBase&)
519 ASSERT(m_sessionState == SessionState::Replaying);
520 ASSERT(m_segmentState == SegmentState::Dispatching);
522 InspectorInstrumentation::playbackHitPosition(&m_page, m_currentPosition);
525 void ReplayController::didDispatchFinalInput()
527 ASSERT(m_segmentState == SegmentState::Dispatching);
529 // No more segments left to replay; stop.
530 if (m_currentPosition.segmentOffset + 1 == m_loadedSession->size()) {
531 // Normally the position is adjusted when loading the next segment.
532 m_currentPosition.segmentOffset++;
533 m_currentPosition.inputOffset = 0;
540 loadSegmentAtIndex(m_currentPosition.segmentOffset + 1);
544 } // namespace WebCore
546 #endif // ENABLE(WEB_REPLAY)