ReplayController.cpp [plain text]
#include "config.h"
#include "ReplayController.h"
#if ENABLE(WEB_REPLAY)
#include "AllReplayInputs.h"
#include "CapturingInputCursor.h"
#include "DOMWindow.h"
#include "DocumentLoader.h"
#include "Frame.h"
#include "FrameTree.h"
#include "InspectorInstrumentation.h"
#include "Location.h"
#include "Logging.h"
#include "MainFrame.h"
#include "Page.h"
#include "ReplaySession.h"
#include "ReplaySessionSegment.h"
#include "ReplayingInputCursor.h"
#include "ScriptController.h"
#include "SerializationMethods.h"
#include "Settings.h"
#include "UserInputBridge.h"
#include "WebReplayInputs.h"
#include <replay/EmptyInputCursor.h>
#include <wtf/text/CString.h>
#if ENABLE(ASYNC_SCROLLING)
#include "ScrollingCoordinator.h"
#endif
namespace WebCore {
static void logDispatchedDOMEvent(const Event& event, bool eventIsUnrelated)
{
#if !LOG_DISABLED
EventTarget* target = event.target();
if (!target)
return;
if (Node* node = target->toNode()) {
LOG(WebReplay, "%-20s --->%s DOM event: type=%s, target=%lu/node[%p] %s\n", "ReplayEvents",
(eventIsUnrelated) ? "Unrelated" : "Dispatching",
event.type().string().utf8().data(),
frameIndexFromDocument((node->inDocument()) ? &node->document() : node->ownerDocument()),
node,
node->nodeName().utf8().data());
} else if (DOMWindow* window = target->toDOMWindow()) {
LOG(WebReplay, "%-20s --->%s DOM event: type=%s, target=%lu/window[%p] %s\n", "ReplayEvents",
(eventIsUnrelated) ? "Unrelated" : "Dispatching",
event.type().string().utf8().data(),
frameIndexFromDocument(window->document()),
window,
window->location()->href().utf8().data());
}
#else
UNUSED_PARAM(event);
UNUSED_PARAM(eventIsUnrelated);
#endif
}
ReplayController::ReplayController(Page& page)
: m_page(page)
, m_loadedSegment(nullptr)
, m_loadedSession(ReplaySession::create())
, m_emptyCursor(EmptyInputCursor::create())
, m_activeCursor(nullptr)
, m_targetPosition(ReplayPosition(0, 0))
, m_currentPosition(ReplayPosition(0, 0))
, m_segmentState(SegmentState::Unloaded)
, m_sessionState(SessionState::Inactive)
, m_dispatchSpeed(DispatchSpeed::FastForward)
{
}
void ReplayController::setForceDeterministicSettings(bool shouldForceDeterministicBehavior)
{
ASSERT(shouldForceDeterministicBehavior ^ (m_sessionState == SessionState::Inactive));
if (shouldForceDeterministicBehavior) {
m_savedSettings.usesPageCache = m_page.settings().usesPageCache();
m_page.settings().setUsesPageCache(false);
} else {
m_page.settings().setUsesPageCache(m_savedSettings.usesPageCache);
}
#if ENABLE(ASYNC_SCROLLING)
if (ScrollingCoordinator* scrollingCoordinator = m_page.scrollingCoordinator())
scrollingCoordinator->replaySessionStateDidChange();
#endif
}
void ReplayController::setSessionState(SessionState state)
{
ASSERT(state != m_sessionState);
switch (m_sessionState) {
case SessionState::Capturing:
ASSERT(state == SessionState::Inactive);
m_sessionState = state;
m_page.userInputBridge().setState(UserInputBridge::State::Open);
break;
case SessionState::Inactive:
m_sessionState = state;
m_page.userInputBridge().setState(state == SessionState::Capturing ? UserInputBridge::State::Capturing : UserInputBridge::State::Replaying);
break;
case SessionState::Replaying:
ASSERT(state == SessionState::Inactive);
m_sessionState = state;
m_page.userInputBridge().setState(UserInputBridge::State::Open);
break;
}
}
void ReplayController::switchSession(PassRefPtr<ReplaySession> session)
{
ASSERT(m_segmentState == SegmentState::Unloaded);
ASSERT(m_sessionState == SessionState::Inactive);
m_loadedSession = session;
m_currentPosition = ReplayPosition(0, 0);
LOG(WebReplay, "%-20sSwitching sessions from %p to %p.\n", "ReplayController", m_loadedSession.get(), session.get());
InspectorInstrumentation::sessionLoaded(&m_page, m_loadedSession);
}
void ReplayController::createSegment()
{
ASSERT(m_sessionState == SessionState::Capturing);
ASSERT(m_segmentState == SegmentState::Unloaded);
m_segmentState = SegmentState::Appending;
m_loadedSegment = ReplaySessionSegment::create();
LOG(WebReplay, "%-20s Created segment: %p.\n", "ReplayController", m_loadedSegment.get());
InspectorInstrumentation::segmentCreated(&m_page, m_loadedSegment);
m_activeCursor = m_loadedSegment->createCapturingCursor(m_page);
m_activeCursor->appendInput<BeginSegmentSentinel>();
std::unique_ptr<InitialNavigation> navigationInput = InitialNavigation::createFromPage(m_page);
navigationInput->dispatch(*this);
m_activeCursor->storeInput(WTF::move(navigationInput));
}
void ReplayController::completeSegment()
{
ASSERT(m_sessionState == SessionState::Capturing);
ASSERT(m_segmentState == SegmentState::Appending);
m_activeCursor->appendInput<EndSegmentSentinel>();
RefPtr<ReplaySessionSegment> segment = m_loadedSegment;
m_segmentState = SegmentState::Loaded;
bool shouldSuppressNotifications = true;
unloadSegment(shouldSuppressNotifications);
LOG(WebReplay, "%-20s Completed segment: %p.\n", "ReplayController", segment.get());
InspectorInstrumentation::segmentCompleted(&m_page, segment);
m_loadedSession->appendSegment(segment);
InspectorInstrumentation::sessionModified(&m_page, m_loadedSession);
}
void ReplayController::loadSegmentAtIndex(size_t segmentIndex)
{
ASSERT(segmentIndex < m_loadedSession->size());
RefPtr<ReplaySessionSegment> segment = m_loadedSession->at(segmentIndex);
ASSERT(m_sessionState == SessionState::Replaying);
ASSERT(m_segmentState == SegmentState::Unloaded);
ASSERT(segment);
ASSERT(!m_loadedSegment);
m_loadedSegment = segment;
m_segmentState = SegmentState::Loaded;
m_currentPosition.segmentOffset = segmentIndex;
m_currentPosition.inputOffset = 0;
m_activeCursor = m_loadedSegment->createReplayingCursor(m_page, this);
LOG(WebReplay, "%-20sLoading segment: %p.\n", "ReplayController", segment.get());
InspectorInstrumentation::segmentLoaded(&m_page, segment);
}
void ReplayController::unloadSegment(bool suppressNotifications)
{
ASSERT(m_sessionState != SessionState::Inactive);
ASSERT(m_segmentState == SegmentState::Loaded);
m_segmentState = SegmentState::Unloaded;
LOG(WebReplay, "%-20s Clearing input cursors for page: %p\n", "ReplayController", &m_page);
m_activeCursor = nullptr;
RefPtr<ReplaySessionSegment> unloadedSegment = m_loadedSegment.release();
for (Frame* frame = &m_page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
frame->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_emptyCursor);
frame->document()->setInputCursor(m_emptyCursor);
}
if (!suppressNotifications) {
LOG(WebReplay, "%-20sUnloading segment: %p.\n", "ReplayController", unloadedSegment.get());
InspectorInstrumentation::segmentUnloaded(&m_page);
}
}
void ReplayController::startCapturing()
{
ASSERT(m_sessionState == SessionState::Inactive);
ASSERT(m_segmentState == SegmentState::Unloaded);
setSessionState(SessionState::Capturing);
setForceDeterministicSettings(true);
LOG(WebReplay, "%-20s Starting capture.\n", "ReplayController");
InspectorInstrumentation::captureStarted(&m_page);
m_currentPosition = ReplayPosition(0, 0);
createSegment();
}
void ReplayController::stopCapturing()
{
ASSERT(m_sessionState == SessionState::Capturing);
ASSERT(m_segmentState == SegmentState::Appending);
completeSegment();
setSessionState(SessionState::Inactive);
setForceDeterministicSettings(false);
LOG(WebReplay, "%-20s Stopping capture.\n", "ReplayController");
InspectorInstrumentation::captureStopped(&m_page);
}
void ReplayController::startPlayback()
{
ASSERT(m_sessionState == SessionState::Replaying);
ASSERT(m_segmentState == SegmentState::Loaded);
m_segmentState = SegmentState::Dispatching;
LOG(WebReplay, "%-20s Starting playback to position (segment: %d, input: %d).\n", "ReplayController", m_targetPosition.segmentOffset, m_targetPosition.inputOffset);
InspectorInstrumentation::playbackStarted(&m_page);
dispatcher().setDispatchSpeed(m_dispatchSpeed);
dispatcher().run();
}
void ReplayController::pausePlayback()
{
ASSERT(m_sessionState == SessionState::Replaying);
ASSERT(m_segmentState == SegmentState::Dispatching);
if (dispatcher().isRunning())
dispatcher().pause();
m_segmentState = SegmentState::Loaded;
LOG(WebReplay, "%-20s Pausing playback at position (segment: %d, input: %d).\n", "ReplayController", m_currentPosition.segmentOffset, m_currentPosition.inputOffset);
InspectorInstrumentation::playbackPaused(&m_page, m_currentPosition);
}
void ReplayController::cancelPlayback()
{
ASSERT(m_sessionState == SessionState::Replaying);
ASSERT(m_segmentState != SegmentState::Appending);
if (m_segmentState == SegmentState::Unloaded)
return;
if (m_segmentState == SegmentState::Dispatching)
pausePlayback();
ASSERT(m_segmentState == SegmentState::Loaded);
unloadSegment();
m_sessionState = SessionState::Inactive;
setForceDeterministicSettings(false);
InspectorInstrumentation::playbackFinished(&m_page);
}
void ReplayController::replayToPosition(const ReplayPosition& position, DispatchSpeed speed)
{
ASSERT(m_sessionState != SessionState::Capturing);
ASSERT(m_segmentState == SegmentState::Loaded || m_segmentState == SegmentState::Unloaded);
ASSERT(position.segmentOffset < m_loadedSession->size());
m_dispatchSpeed = speed;
if (m_sessionState != SessionState::Replaying) {
setSessionState(SessionState::Replaying);
setForceDeterministicSettings(true);
}
if (m_segmentState == SegmentState::Unloaded)
loadSegmentAtIndex(position.segmentOffset);
else if (position.segmentOffset != m_currentPosition.segmentOffset || m_currentPosition.inputOffset > position.inputOffset) {
unloadSegment();
loadSegmentAtIndex(position.segmentOffset);
}
ASSERT(m_currentPosition.segmentOffset == position.segmentOffset);
ASSERT(m_loadedSession->at(position.segmentOffset) == m_loadedSegment);
m_targetPosition = position;
startPlayback();
}
void ReplayController::frameNavigated(DocumentLoader* loader)
{
ASSERT(m_sessionState != SessionState::Inactive);
if (m_sessionState == SessionState::Capturing && m_segmentState == SegmentState::Unloaded) {
m_currentPosition = ReplayPosition(m_currentPosition.segmentOffset + 1, 0);
createSegment();
}
loader->frame()->document()->setInputCursor(m_activeCursor.get());
loader->frame()->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_activeCursor.get());
}
void ReplayController::frameDetached(Frame* frame)
{
ASSERT(m_sessionState != SessionState::Inactive);
ASSERT(frame);
if (!frame->document())
return;
if (frame->document()->inputCursor().isCapturing()) {
ASSERT(m_segmentState == SegmentState::Appending);
completeSegment();
}
}
void ReplayController::willDispatchEvent(const Event& event, Frame* frame)
{
EventTarget* target = event.target();
if (!target && !frame)
return;
Document* document = frame ? frame->document() : nullptr;
if (Node* node = target->toNode())
document = node->inDocument() ? &node->document() : node->ownerDocument();
else if (DOMWindow* window = target->toDOMWindow())
document = window->document();
ASSERT(document);
InputCursor& cursor = document->inputCursor();
bool eventIsUnrelated = !cursor.isCapturing() && !cursor.isReplaying();
logDispatchedDOMEvent(event, eventIsUnrelated);
#if ENABLE_AGGRESSIVE_DETERMINISM_CHECKS
if (cursor.isCapturing())
ASSERT(static_cast<CapturingInputCursor&>(cursor).withinEventLoopInputExtent());
else if (cursor.isReplaying())
ASSERT(dispatcher().isDispatching());
#endif
}
PassRefPtr<ReplaySession> ReplayController::loadedSession() const
{
return m_loadedSession;
}
PassRefPtr<ReplaySessionSegment> ReplayController::loadedSegment() const
{
return m_loadedSegment;
}
InputCursor& ReplayController::activeInputCursor() const
{
return m_activeCursor ? *m_activeCursor : *m_emptyCursor;
}
EventLoopInputDispatcher& ReplayController::dispatcher() const
{
ASSERT(m_sessionState == SessionState::Replaying);
ASSERT(m_segmentState == SegmentState::Dispatching);
ASSERT(m_activeCursor);
ASSERT(m_activeCursor->isReplaying());
return static_cast<ReplayingInputCursor&>(*m_activeCursor).dispatcher();
}
void ReplayController::willDispatchInput(const EventLoopInputBase&)
{
ASSERT(m_sessionState == SessionState::Replaying);
ASSERT(m_segmentState == SegmentState::Dispatching);
m_currentPosition.inputOffset++;
if (m_currentPosition == m_targetPosition)
pausePlayback();
}
void ReplayController::didDispatchInput(const EventLoopInputBase&)
{
ASSERT(m_sessionState == SessionState::Replaying);
ASSERT(m_segmentState == SegmentState::Dispatching);
InspectorInstrumentation::playbackHitPosition(&m_page, m_currentPosition);
}
void ReplayController::didDispatchFinalInput()
{
ASSERT(m_segmentState == SegmentState::Dispatching);
if (m_currentPosition.segmentOffset + 1 == m_loadedSession->size()) {
m_currentPosition.segmentOffset++;
m_currentPosition.inputOffset = 0;
cancelPlayback();
return;
}
unloadSegment();
loadSegmentAtIndex(m_currentPosition.segmentOffset + 1);
startPlayback();
}
}
#endif // ENABLE(WEB_REPLAY)