summaryrefslogtreecommitdiff
path: root/Source/WebCore/inspector/InspectorReplayAgent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/inspector/InspectorReplayAgent.cpp')
-rw-r--r--Source/WebCore/inspector/InspectorReplayAgent.cpp513
1 files changed, 513 insertions, 0 deletions
diff --git a/Source/WebCore/inspector/InspectorReplayAgent.cpp b/Source/WebCore/inspector/InspectorReplayAgent.cpp
new file mode 100644
index 000000000..3aaa52a6b
--- /dev/null
+++ b/Source/WebCore/inspector/InspectorReplayAgent.cpp
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2011-2013 University of Washington. All rights reserved.
+ * Copyright (C) 2014, 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "InspectorReplayAgent.h"
+
+#if ENABLE(WEB_REPLAY)
+
+#include "DocumentLoader.h"
+#include "Event.h"
+#include "EventLoopInput.h"
+#include "Frame.h"
+#include "FunctorInputCursor.h"
+#include "InspectorController.h"
+#include "InspectorPageAgent.h"
+#include <inspector/InspectorProtocolObjects.h>
+#include "InstrumentingAgents.h"
+#include "Logging.h"
+#include "Page.h"
+#include "ReplayController.h"
+#include "ReplaySession.h"
+#include "ReplaySessionSegment.h"
+#include "SerializationMethods.h"
+#include "WebReplayInputs.h"
+#include <inspector/InspectorValues.h>
+#include <wtf/text/CString.h>
+#include <wtf/text/WTFString.h>
+
+using namespace Inspector;
+
+namespace WebCore {
+
+static Ref<Inspector::Protocol::Replay::ReplayPosition> buildInspectorObjectForPosition(const ReplayPosition& position)
+{
+ return Inspector::Protocol::Replay::ReplayPosition::create()
+ .setSegmentOffset(position.segmentOffset)
+ .setInputOffset(position.inputOffset)
+ .release();
+}
+
+static Ref<Inspector::Protocol::Replay::ReplayInput> buildInspectorObjectForInput(const NondeterministicInputBase& input, size_t offset)
+{
+ EncodedValue encodedInput = EncodingTraits<NondeterministicInputBase>::encodeValue(input);
+ return Inspector::Protocol::Replay::ReplayInput::create()
+ .setType(input.type())
+ .setOffset(offset)
+ .setData(encodedInput.asObject())
+ .release();
+}
+
+static Ref<Inspector::Protocol::Replay::ReplaySession> buildInspectorObjectForSession(RefPtr<ReplaySession>&& session)
+{
+ auto segments = Inspector::Protocol::Array<SegmentIdentifier>::create();
+
+ for (auto& segment : *session)
+ segments->addItem(static_cast<int>(segment->identifier()));
+
+ return Inspector::Protocol::Replay::ReplaySession::create()
+ .setId(session->identifier())
+ .setTimestamp(session->timestamp())
+ .setSegments(WTFMove(segments))
+ .release();
+}
+
+static Inspector::Protocol::Replay::SessionState buildInspectorObjectForSessionState(WebCore::SessionState sessionState)
+{
+ switch (sessionState) {
+ case WebCore::SessionState::Capturing: return Inspector::Protocol::Replay::SessionState::Capturing;
+ case WebCore::SessionState::Inactive: return Inspector::Protocol::Replay::SessionState::Inactive;
+ case WebCore::SessionState::Replaying: return Inspector::Protocol::Replay::SessionState::Replaying;
+ }
+
+ RELEASE_ASSERT_NOT_REACHED();
+ return Inspector::Protocol::Replay::SessionState::Inactive;
+}
+
+static Inspector::Protocol::Replay::SegmentState buildInspectorObjectForSegmentState(WebCore::SegmentState segmentState)
+{
+ switch (segmentState) {
+ case WebCore::SegmentState::Appending: return Inspector::Protocol::Replay::SegmentState::Appending;
+ case WebCore::SegmentState::Unloaded: return Inspector::Protocol::Replay::SegmentState::Unloaded;
+ case WebCore::SegmentState::Loaded: return Inspector::Protocol::Replay::SegmentState::Loaded;
+ case WebCore::SegmentState::Dispatching: return Inspector::Protocol::Replay::SegmentState::Dispatching;
+ }
+
+ RELEASE_ASSERT_NOT_REACHED();
+ return Inspector::Protocol::Replay::SegmentState::Unloaded;
+}
+
+class SerializeInputToJSONFunctor {
+public:
+ typedef RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Replay::ReplayInput>> ReturnType;
+
+ SerializeInputToJSONFunctor()
+ : m_inputs(Inspector::Protocol::Array<Inspector::Protocol::Replay::ReplayInput>::create()) { }
+ ~SerializeInputToJSONFunctor() { }
+
+ void operator()(size_t index, const NondeterministicInputBase* input)
+ {
+ LOG(WebReplay, "%-25s Writing %5zu: %s\n", "[SerializeInput]", index, input->type().ascii().data());
+
+ if (RefPtr<Inspector::Protocol::Replay::ReplayInput> serializedInput = buildInspectorObjectForInput(*input, index))
+ m_inputs->addItem(WTFMove(serializedInput));
+ }
+
+ ReturnType returnValue() { return WTFMove(m_inputs); }
+private:
+ RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Replay::ReplayInput>> m_inputs;
+};
+
+static Ref<Inspector::Protocol::Replay::SessionSegment> buildInspectorObjectForSegment(RefPtr<ReplaySessionSegment>&& segment)
+{
+ auto queuesObject = Inspector::Protocol::Array<Inspector::Protocol::Replay::ReplayInputQueue>::create();
+
+ for (size_t i = 0; i < static_cast<size_t>(InputQueue::Count); i++) {
+ SerializeInputToJSONFunctor collector;
+ InputQueue queue = static_cast<InputQueue>(i);
+ RefPtr<FunctorInputCursor> functorCursor = FunctorInputCursor::create(segment.copyRef());
+ RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Replay::ReplayInput>> queueInputs = functorCursor->forEachInputInQueue(queue, collector);
+
+ auto queueObject = Inspector::Protocol::Replay::ReplayInputQueue::create()
+ .setType(EncodingTraits<InputQueue>::encodeValue(queue).convertTo<String>())
+ .setInputs(queueInputs)
+ .release();
+ queuesObject->addItem(WTFMove(queueObject));
+ }
+
+ return Inspector::Protocol::Replay::SessionSegment::create()
+ .setId(segment->identifier())
+ .setTimestamp(segment->timestamp())
+ .setQueues(WTFMove(queuesObject))
+ .release();
+}
+
+InspectorReplayAgent::InspectorReplayAgent(PageAgentContext& context)
+ : InspectorAgentBase(ASCIILiteral("Replay"), context)
+ , m_frontendDispatcher(std::make_unique<Inspector::ReplayFrontendDispatcher>(context.frontendRouter))
+ , m_backendDispatcher(Inspector::ReplayBackendDispatcher::create(context.backendDispatcher, this))
+ , m_page(context.inspectedPage)
+{
+}
+
+InspectorReplayAgent::~InspectorReplayAgent()
+{
+ ASSERT(!m_sessionsMap.size());
+ ASSERT(!m_segmentsMap.size());
+}
+
+WebCore::SessionState InspectorReplayAgent::sessionState() const
+{
+ return m_page.replayController().sessionState();
+}
+
+void InspectorReplayAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
+{
+ m_instrumentingAgents.setInspectorReplayAgent(this);
+ ASSERT(sessionState() == WebCore::SessionState::Inactive);
+
+ // Keep track of the (default) session currently loaded by ReplayController,
+ // and any segments within the session.
+ RefPtr<ReplaySession> session = m_page.replayController().loadedSession();
+ m_sessionsMap.add(session->identifier(), session);
+
+ for (auto& segment : *session)
+ m_segmentsMap.add(segment->identifier(), segment);
+}
+
+void InspectorReplayAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
+{
+ m_instrumentingAgents.setInspectorReplayAgent(nullptr);
+
+ // Drop references to all sessions and segments.
+ m_sessionsMap.clear();
+ m_segmentsMap.clear();
+}
+
+void InspectorReplayAgent::frameNavigated(Frame& frame)
+{
+ if (sessionState() != WebCore::SessionState::Inactive)
+ m_page.replayController().frameNavigated(frame);
+}
+
+void InspectorReplayAgent::frameDetached(Frame& frame)
+{
+ if (sessionState() != WebCore::SessionState::Inactive)
+ m_page.replayController().frameDetached(frame);
+}
+
+void InspectorReplayAgent::willDispatchEvent(const Event& event, Frame* frame)
+{
+ if (sessionState() != WebCore::SessionState::Inactive)
+ m_page.replayController().willDispatchEvent(event, frame);
+}
+
+void InspectorReplayAgent::sessionCreated(RefPtr<ReplaySession>&& session)
+{
+ auto result = m_sessionsMap.add(session->identifier(), session);
+ // Can't have two sessions with same identifier.
+ ASSERT_UNUSED(result, result.isNewEntry);
+
+ m_frontendDispatcher->sessionCreated(session->identifier());
+}
+
+void InspectorReplayAgent::sessionModified(RefPtr<ReplaySession>&& session)
+{
+ m_frontendDispatcher->sessionModified(session->identifier());
+}
+
+void InspectorReplayAgent::sessionLoaded(RefPtr<ReplaySession>&& session)
+{
+ // In case we didn't know about the loaded session, add here.
+ m_sessionsMap.add(session->identifier(), session);
+
+ m_frontendDispatcher->sessionLoaded(session->identifier());
+}
+
+void InspectorReplayAgent::segmentCreated(RefPtr<ReplaySessionSegment>&& segment)
+{
+ auto result = m_segmentsMap.add(segment->identifier(), segment);
+ // Can't have two segments with the same identifier.
+ ASSERT_UNUSED(result, result.isNewEntry);
+
+ m_frontendDispatcher->segmentCreated(segment->identifier());
+}
+
+void InspectorReplayAgent::segmentCompleted(RefPtr<ReplaySessionSegment>&& segment)
+{
+ m_frontendDispatcher->segmentCompleted(segment->identifier());
+}
+
+void InspectorReplayAgent::segmentLoaded(RefPtr<ReplaySessionSegment>&& segment)
+{
+ // In case we didn't know about the loaded segment, add here.
+ m_segmentsMap.add(segment->identifier(), segment.copyRef());
+
+ m_frontendDispatcher->segmentLoaded(segment->identifier());
+}
+
+void InspectorReplayAgent::segmentUnloaded()
+{
+ m_frontendDispatcher->segmentUnloaded();
+}
+
+void InspectorReplayAgent::captureStarted()
+{
+ LOG(WebReplay, "-----CAPTURE START-----");
+
+ m_frontendDispatcher->captureStarted();
+}
+
+void InspectorReplayAgent::captureStopped()
+{
+ LOG(WebReplay, "-----CAPTURE STOP-----");
+
+ m_frontendDispatcher->captureStopped();
+}
+
+void InspectorReplayAgent::playbackStarted()
+{
+ LOG(WebReplay, "-----REPLAY START-----");
+
+ m_frontendDispatcher->playbackStarted();
+}
+
+void InspectorReplayAgent::playbackPaused(const ReplayPosition& position)
+{
+ LOG(WebReplay, "-----REPLAY PAUSED-----");
+
+ m_frontendDispatcher->playbackPaused(buildInspectorObjectForPosition(position));
+}
+
+void InspectorReplayAgent::playbackHitPosition(const ReplayPosition& position)
+{
+ m_frontendDispatcher->playbackHitPosition(buildInspectorObjectForPosition(position), monotonicallyIncreasingTime());
+}
+
+void InspectorReplayAgent::playbackFinished()
+{
+ LOG(WebReplay, "-----REPLAY FINISHED-----");
+
+ m_frontendDispatcher->playbackFinished();
+}
+
+void InspectorReplayAgent::startCapturing(ErrorString& errorString)
+{
+ if (sessionState() != WebCore::SessionState::Inactive) {
+ errorString = ASCIILiteral("Can't start capturing if the session is already capturing or replaying.");
+ return;
+ }
+
+ m_page.replayController().startCapturing();
+}
+
+void InspectorReplayAgent::stopCapturing(ErrorString& errorString)
+{
+ if (sessionState() != WebCore::SessionState::Capturing) {
+ errorString = ASCIILiteral("Can't stop capturing if capture is not in progress.");
+ return;
+ }
+
+ m_page.replayController().stopCapturing();
+}
+
+void InspectorReplayAgent::replayToPosition(ErrorString& errorString, const InspectorObject& positionObject, bool fastReplay)
+{
+ ReplayPosition position;
+ if (!positionObject.getInteger(ASCIILiteral("segmentOffset"), position.segmentOffset)) {
+ errorString = ASCIILiteral("Couldn't decode ReplayPosition segment offset provided to ReplayAgent.replayToPosition.");
+ return;
+ }
+
+ if (!positionObject.getInteger(ASCIILiteral("inputOffset"), position.inputOffset)) {
+ errorString = ASCIILiteral("Couldn't decode ReplayPosition input offset provided to ReplayAgent.replayToPosition.");
+ return;
+ }
+
+ if (sessionState() == WebCore::SessionState::Capturing) {
+ errorString = ASCIILiteral("Can't start replay while capture is in progress.");
+ return;
+ }
+
+ m_page.replayController().replayToPosition(position, (fastReplay) ? DispatchSpeed::FastForward : DispatchSpeed::RealTime);
+}
+
+void InspectorReplayAgent::replayToCompletion(ErrorString& errorString, bool fastReplay)
+{
+ if (sessionState() == WebCore::SessionState::Capturing) {
+ errorString = ASCIILiteral("Can't start replay while capture is in progress.");
+ return;
+ }
+
+ m_page.replayController().replayToCompletion((fastReplay) ? DispatchSpeed::FastForward : DispatchSpeed::RealTime);
+}
+
+void InspectorReplayAgent::pausePlayback(ErrorString& errorString)
+{
+ if (sessionState() != WebCore::SessionState::Replaying) {
+ errorString = ASCIILiteral("Can't pause playback if playback is not in progress.");
+ return;
+ }
+
+ m_page.replayController().pausePlayback();
+}
+
+void InspectorReplayAgent::cancelPlayback(ErrorString& errorString)
+{
+ if (sessionState() == WebCore::SessionState::Capturing) {
+ errorString = ASCIILiteral("Can't cancel playback if capture is in progress.");
+ return;
+ }
+
+ m_page.replayController().cancelPlayback();
+}
+
+void InspectorReplayAgent::switchSession(ErrorString& errorString, Inspector::Protocol::Replay::SessionIdentifier identifier)
+{
+ ASSERT_ARG(identifier, identifier > 0);
+
+ if (sessionState() != WebCore::SessionState::Inactive) {
+ errorString = ASCIILiteral("Can't switch sessions unless the session is neither capturing or replaying.");
+ return;
+ }
+
+ RefPtr<ReplaySession> session = findSession(errorString, identifier);
+ if (!session)
+ return;
+
+ m_page.replayController().switchSession(WTFMove(session));
+}
+
+void InspectorReplayAgent::insertSessionSegment(ErrorString& errorString, Inspector::Protocol::Replay::SessionIdentifier sessionIdentifier, SegmentIdentifier segmentIdentifier, int segmentIndex)
+{
+ ASSERT_ARG(sessionIdentifier, sessionIdentifier > 0);
+ ASSERT_ARG(segmentIdentifier, segmentIdentifier > 0);
+ ASSERT_ARG(segmentIndex, segmentIndex >= 0);
+
+ RefPtr<ReplaySession> session = findSession(errorString, sessionIdentifier);
+ RefPtr<ReplaySessionSegment> segment = findSegment(errorString, segmentIdentifier);
+
+ if (!session || !segment)
+ return;
+
+ if (static_cast<size_t>(segmentIndex) > session->size()) {
+ errorString = ASCIILiteral("Invalid segment index.");
+ return;
+ }
+
+ if (session == m_page.replayController().loadedSession() && sessionState() != WebCore::SessionState::Inactive) {
+ errorString = ASCIILiteral("Can't modify a loaded session unless the session is inactive.");
+ return;
+ }
+
+ session->insertSegment(segmentIndex, WTFMove(segment));
+ sessionModified(WTFMove(session));
+}
+
+void InspectorReplayAgent::removeSessionSegment(ErrorString& errorString, Inspector::Protocol::Replay::SessionIdentifier identifier, int segmentIndex)
+{
+ ASSERT_ARG(identifier, identifier > 0);
+ ASSERT_ARG(segmentIndex, segmentIndex >= 0);
+
+ RefPtr<ReplaySession> session = findSession(errorString, identifier);
+
+ if (!session)
+ return;
+
+ if (static_cast<size_t>(segmentIndex) >= session->size()) {
+ errorString = ASCIILiteral("Invalid segment index.");
+ return;
+ }
+
+ if (session == m_page.replayController().loadedSession() && sessionState() != WebCore::SessionState::Inactive) {
+ errorString = ASCIILiteral("Can't modify a loaded session unless the session is inactive.");
+ return;
+ }
+
+ session->removeSegment(segmentIndex);
+ sessionModified(WTFMove(session));
+}
+
+RefPtr<ReplaySession> InspectorReplayAgent::findSession(ErrorString& errorString, SessionIdentifier identifier)
+{
+ ASSERT_ARG(identifier, identifier > 0);
+
+ auto it = m_sessionsMap.find(identifier);
+ if (it == m_sessionsMap.end()) {
+ errorString = ASCIILiteral("Couldn't find session with specified identifier");
+ return nullptr;
+ }
+
+ return it->value;
+}
+
+RefPtr<ReplaySessionSegment> InspectorReplayAgent::findSegment(ErrorString& errorString, SegmentIdentifier identifier)
+{
+ ASSERT_ARG(identifier, identifier > 0);
+
+ auto it = m_segmentsMap.find(identifier);
+ if (it == m_segmentsMap.end()) {
+ errorString = ASCIILiteral("Couldn't find segment with specified identifier");
+ return nullptr;
+ }
+
+ return it->value;
+}
+
+void InspectorReplayAgent::currentReplayState(ErrorString&, Inspector::Protocol::Replay::SessionIdentifier* sessionIdentifier, Inspector::Protocol::OptOutput<Inspector::Protocol::Replay::SegmentIdentifier>* segmentIdentifier, Inspector::Protocol::Replay::SessionState* sessionState, Inspector::Protocol::Replay::SegmentState* segmentState, RefPtr<Inspector::Protocol::Replay::ReplayPosition>& replayPosition)
+{
+ *sessionState = buildInspectorObjectForSessionState(m_page.replayController().sessionState());
+ *segmentState = buildInspectorObjectForSegmentState(m_page.replayController().segmentState());
+
+ *sessionIdentifier = m_page.replayController().loadedSession()->identifier();
+ if (m_page.replayController().loadedSegment())
+ *segmentIdentifier = m_page.replayController().loadedSegment()->identifier();
+
+ replayPosition = buildInspectorObjectForPosition(m_page.replayController().currentPosition());
+}
+
+void InspectorReplayAgent::getAvailableSessions(ErrorString&, RefPtr<Inspector::Protocol::Array<SessionIdentifier>>& sessionsList)
+{
+ sessionsList = Inspector::Protocol::Array<Inspector::Protocol::Replay::SessionIdentifier>::create();
+ for (auto& pair : m_sessionsMap)
+ sessionsList->addItem(pair.key);
+}
+
+void InspectorReplayAgent::getSessionData(ErrorString& errorString, Inspector::Protocol::Replay::SessionIdentifier identifier, RefPtr<Inspector::Protocol::Replay::ReplaySession>& serializedObject)
+{
+ RefPtr<ReplaySession> session = findSession(errorString, identifier);
+ if (!session) {
+ errorString = ASCIILiteral("Couldn't find the specified session.");
+ return;
+ }
+
+ serializedObject = buildInspectorObjectForSession(WTFMove(session));
+}
+
+void InspectorReplayAgent::getSegmentData(ErrorString& errorString, Inspector::Protocol::Replay::SegmentIdentifier identifier, RefPtr<Inspector::Protocol::Replay::SessionSegment>& serializedObject)
+{
+ RefPtr<ReplaySessionSegment> segment = findSegment(errorString, identifier);
+ if (!segment) {
+ errorString = ASCIILiteral("Couldn't find the specified segment.");
+ return;
+ }
+
+ serializedObject = buildInspectorObjectForSegment(WTFMove(segment));
+}
+
+} // namespace WebCore
+#endif // ENABLE(WEB_REPLAY)