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