/* * 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 #include "InstrumentingAgents.h" #include "Logging.h" #include "Page.h" #include "ReplayController.h" #include "ReplaySession.h" #include "ReplaySessionSegment.h" #include "SerializationMethods.h" #include "WebReplayInputs.h" // For EncodingTraits. #include #include #include using namespace Inspector; namespace WebCore { static Ref buildInspectorObjectForPosition(const ReplayPosition& position) { return Inspector::Protocol::Replay::ReplayPosition::create() .setSegmentOffset(position.segmentOffset) .setInputOffset(position.inputOffset) .release(); } static Ref buildInspectorObjectForInput(const NondeterministicInputBase& input, size_t offset) { EncodedValue encodedInput = EncodingTraits::encodeValue(input); return Inspector::Protocol::Replay::ReplayInput::create() .setType(input.type()) .setOffset(offset) .setData(encodedInput.asObject()) .release(); } static Ref buildInspectorObjectForSession(RefPtr&& session) { auto segments = Inspector::Protocol::Array::create(); for (auto& segment : *session) segments->addItem(static_cast(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> ReturnType; SerializeInputToJSONFunctor() : m_inputs(Inspector::Protocol::Array::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 serializedInput = buildInspectorObjectForInput(*input, index)) m_inputs->addItem(WTFMove(serializedInput)); } ReturnType returnValue() { return WTFMove(m_inputs); } private: RefPtr> m_inputs; }; static Ref buildInspectorObjectForSegment(RefPtr&& segment) { auto queuesObject = Inspector::Protocol::Array::create(); for (size_t i = 0; i < static_cast(InputQueue::Count); i++) { SerializeInputToJSONFunctor collector; InputQueue queue = static_cast(i); RefPtr functorCursor = FunctorInputCursor::create(segment.copyRef()); RefPtr> queueInputs = functorCursor->forEachInputInQueue(queue, collector); auto queueObject = Inspector::Protocol::Replay::ReplayInputQueue::create() .setType(EncodingTraits::encodeValue(queue).convertTo()) .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(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 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(DocumentLoader* loader) { if (sessionState() != WebCore::SessionState::Inactive) m_page.replayController().frameNavigated(loader); } 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&& 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&& session) { m_frontendDispatcher->sessionModified(session->identifier()); } void InspectorReplayAgent::sessionLoaded(RefPtr&& 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&& 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&& segment) { m_frontendDispatcher->segmentCompleted(segment->identifier()); } void InspectorReplayAgent::segmentLoaded(RefPtr&& 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 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 session = findSession(errorString, sessionIdentifier); RefPtr segment = findSegment(errorString, segmentIdentifier); if (!session || !segment) return; if (static_cast(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 session = findSession(errorString, identifier); if (!session) return; if (static_cast(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 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 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* segmentIdentifier, Inspector::Protocol::Replay::SessionState* sessionState, Inspector::Protocol::Replay::SegmentState* segmentState, RefPtr& 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>& sessionsList) { sessionsList = Inspector::Protocol::Array::create(); for (auto& pair : m_sessionsMap) sessionsList->addItem(pair.key); } void InspectorReplayAgent::getSessionData(ErrorString& errorString, Inspector::Protocol::Replay::SessionIdentifier identifier, RefPtr& serializedObject) { RefPtr 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& serializedObject) { RefPtr 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)