diff options
Diffstat (limited to 'Source/WebCore/inspector/InspectorTimelineAgent.cpp')
-rw-r--r-- | Source/WebCore/inspector/InspectorTimelineAgent.cpp | 745 |
1 files changed, 423 insertions, 322 deletions
diff --git a/Source/WebCore/inspector/InspectorTimelineAgent.cpp b/Source/WebCore/inspector/InspectorTimelineAgent.cpp index f8a6c38a3..1f0d0576d 100644 --- a/Source/WebCore/inspector/InspectorTimelineAgent.cpp +++ b/Source/WebCore/inspector/InspectorTimelineAgent.cpp @@ -1,5 +1,7 @@ /* * Copyright (C) 2013 Google Inc. All rights reserved. +* Copyright (C) 2014 University of Washington. +* Copyright (C) 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 @@ -29,62 +31,133 @@ */ #include "config.h" - -#if ENABLE(INSPECTOR) - #include "InspectorTimelineAgent.h" #include "Event.h" #include "Frame.h" -#include "FrameView.h" -#include "IdentifiersFactory.h" -#include "InspectorClient.h" -#include "InspectorCounters.h" -#include "InspectorInstrumentation.h" #include "InspectorMemoryAgent.h" #include "InspectorPageAgent.h" -#include "InspectorWebFrontendDispatchers.h" #include "InstrumentingAgents.h" -#include "IntRect.h" #include "JSDOMWindow.h" -#include "RenderElement.h" +#include "PageScriptDebugServer.h" #include "RenderView.h" -#include "ResourceRequest.h" -#include "ResourceResponse.h" +#include "ScriptState.h" #include "TimelineRecordFactory.h" -#include <wtf/CurrentTime.h> +#include "WebConsoleAgent.h" +#include <inspector/ConsoleMessage.h> +#include <inspector/ScriptBreakpoint.h> +#include <inspector/agents/InspectorDebuggerAgent.h> +#include <inspector/agents/InspectorHeapAgent.h> +#include <inspector/agents/InspectorScriptProfilerAgent.h> +#include <wtf/Stopwatch.h> + +#if PLATFORM(IOS) +#include "RuntimeApplicationChecks.h" +#include "WebCoreThreadInternal.h" +#endif + +#if PLATFORM(COCOA) +#include "RunLoopObserver.h" +#endif using namespace Inspector; namespace WebCore { -void TimelineTimeConverter::reset() +#if PLATFORM(COCOA) +static const CFIndex frameStopRunLoopOrder = (CFIndex)RunLoopObserver::WellKnownRunLoopOrders::CoreAnimationCommit + 1; + +static CFRunLoopRef currentRunLoop() +{ +#if PLATFORM(IOS) + // A race condition during WebView deallocation can lead to a crash if the layer sync run loop + // observer is added to the main run loop <rdar://problem/9798550>. However, for responsiveness, + // we still allow this, see <rdar://problem/7403328>. Since the race condition and subsequent + // crash are especially troublesome for iBooks, we never allow the observer to be added to the + // main run loop in iBooks. + if (IOSApplication::isIBooks()) + return WebThreadRunLoop(); +#endif + return CFRunLoopGetCurrent(); +} +#endif + +InspectorTimelineAgent::InspectorTimelineAgent(WebAgentContext& context, InspectorScriptProfilerAgent* scriptProfileAgent, InspectorHeapAgent* heapAgent, InspectorPageAgent* pageAgent) + : InspectorAgentBase(ASCIILiteral("Timeline"), context) + , m_frontendDispatcher(std::make_unique<Inspector::TimelineFrontendDispatcher>(context.frontendRouter)) + , m_backendDispatcher(Inspector::TimelineBackendDispatcher::create(context.backendDispatcher, this)) + , m_scriptProfilerAgent(scriptProfileAgent) + , m_heapAgent(heapAgent) + , m_pageAgent(pageAgent) { - m_startOffset = monotonicallyIncreasingTime() - currentTime(); } InspectorTimelineAgent::~InspectorTimelineAgent() { } -void InspectorTimelineAgent::didCreateFrontendAndBackend(Inspector::InspectorFrontendChannel* frontendChannel, InspectorBackendDispatcher* backendDispatcher) +void InspectorTimelineAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*) +{ + m_instrumentingAgents.setPersistentInspectorTimelineAgent(this); +} + +void InspectorTimelineAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason) +{ + m_instrumentingAgents.setPersistentInspectorTimelineAgent(nullptr); + + ErrorString unused; + stop(unused); + + m_autoCaptureEnabled = false; + m_instruments.clear(); +} + +void InspectorTimelineAgent::start(ErrorString&, const int* maxCallStackDepth) +{ + m_enabledFromFrontend = true; + + internalStart(maxCallStackDepth); +} + +void InspectorTimelineAgent::stop(ErrorString&) +{ + internalStop(); + + m_enabledFromFrontend = false; +} + +void InspectorTimelineAgent::setAutoCaptureEnabled(ErrorString&, bool enabled) { - m_frontendDispatcher = std::make_unique<InspectorTimelineFrontendDispatcher>(frontendChannel); - m_backendDispatcher = InspectorTimelineBackendDispatcher::create(backendDispatcher, this); + m_autoCaptureEnabled = enabled; } -void InspectorTimelineAgent::willDestroyFrontendAndBackend(InspectorDisconnectReason) +void InspectorTimelineAgent::setInstruments(ErrorString& errorString, const InspectorArray& instruments) { - m_frontendDispatcher = nullptr; - m_backendDispatcher.clear(); + Vector<Protocol::Timeline::Instrument> newInstruments; + newInstruments.reserveCapacity(instruments.length()); + + for (auto instrumentValue : instruments) { + String enumValueString; + if (!instrumentValue->asString(enumValueString)) { + errorString = ASCIILiteral("Unexpected type in instruments list, should be string"); + return; + } + + std::optional<Protocol::Timeline::Instrument> instrumentType = Protocol::InspectorHelpers::parseEnumValueFromString<Protocol::Timeline::Instrument>(enumValueString); + if (!instrumentType) { + errorString = makeString("Unexpected enum value: ", enumValueString); + return; + } + + newInstruments.uncheckedAppend(*instrumentType); + } - ErrorString error; - stop(&error); + m_instruments.swap(newInstruments); } -void InspectorTimelineAgent::start(ErrorString*, const int* maxCallStackDepth, const bool* includeDomCounters) +void InspectorTimelineAgent::internalStart(const int* maxCallStackDepth) { - if (!m_frontendDispatcher) + if (m_enabled) return; if (maxCallStackDepth && *maxCallStackDepth > 0) @@ -92,46 +165,134 @@ void InspectorTimelineAgent::start(ErrorString*, const int* maxCallStackDepth, c else m_maxCallStackDepth = 5; - if (includeDomCounters) - m_includeDOMCounters = *includeDomCounters; + m_instrumentingAgents.setInspectorTimelineAgent(this); - m_timeConverter.reset(); + m_environment.scriptDebugServer().addListener(this); - m_instrumentingAgents->setInspectorTimelineAgent(this); m_enabled = true; + + // FIXME: Abstract away platform-specific code once https://bugs.webkit.org/show_bug.cgi?id=142748 is fixed. + +#if PLATFORM(COCOA) + m_frameStartObserver = std::make_unique<RunLoopObserver>(0, [this]() { + if (!m_enabled || m_environment.scriptDebugServer().isPaused()) + return; + + if (!m_runLoopNestingLevel) + pushCurrentRecord(InspectorObject::create(), TimelineRecordType::RenderingFrame, false, nullptr); + m_runLoopNestingLevel++; + }); + + m_frameStopObserver = std::make_unique<RunLoopObserver>(frameStopRunLoopOrder, [this]() { + if (!m_enabled || m_environment.scriptDebugServer().isPaused()) + return; + + ASSERT(m_runLoopNestingLevel > 0); + m_runLoopNestingLevel--; + if (m_runLoopNestingLevel) + return; + + if (m_startedComposite) + didComposite(); + + didCompleteCurrentRecord(TimelineRecordType::RenderingFrame); + }); + + m_frameStartObserver->schedule(currentRunLoop(), kCFRunLoopEntry | kCFRunLoopAfterWaiting); + m_frameStopObserver->schedule(currentRunLoop(), kCFRunLoopExit | kCFRunLoopBeforeWaiting); + + // Create a runloop record and increment the runloop nesting level, to capture the current turn of the main runloop + // (which is the outer runloop if recording started while paused in the debugger). + pushCurrentRecord(InspectorObject::create(), TimelineRecordType::RenderingFrame, false, nullptr); + + m_runLoopNestingLevel = 1; +#endif + + m_frontendDispatcher->recordingStarted(timestamp()); } -void InspectorTimelineAgent::stop(ErrorString*) +void InspectorTimelineAgent::internalStop() { if (!m_enabled) return; - m_weakFactory.revokeAll(); - m_instrumentingAgents->setInspectorTimelineAgent(nullptr); + m_instrumentingAgents.setInspectorTimelineAgent(nullptr); + + m_environment.scriptDebugServer().removeListener(this, true); + +#if PLATFORM(COCOA) + m_frameStartObserver = nullptr; + m_frameStopObserver = nullptr; + m_runLoopNestingLevel = 0; + + // Complete all pending records to prevent discarding events that are currently in progress. + while (!m_recordStack.isEmpty()) + didCompleteCurrentRecord(m_recordStack.last().type); +#endif clearRecordStack(); m_enabled = false; -} + m_startedComposite = false; + m_autoCapturePhase = AutoCapturePhase::None; -void InspectorTimelineAgent::canMonitorMainThread(ErrorString*, bool* result) -{ - *result = m_client && m_client->canMonitorMainThread(); + m_frontendDispatcher->recordingStopped(timestamp()); } -void InspectorTimelineAgent::supportsFrameInstrumentation(ErrorString*, bool* result) +double InspectorTimelineAgent::timestamp() { - *result = m_client && m_client->supportsFrameInstrumentation(); + return m_environment.executionStopwatch()->elapsedTime(); } -void InspectorTimelineAgent::didBeginFrame() +void InspectorTimelineAgent::startFromConsole(JSC::ExecState* exec, const String& title) { - m_pendingFrameRecord = TimelineRecordFactory::createGenericRecord(timestamp(), 0); + // Allow duplicate unnamed profiles. Disallow duplicate named profiles. + if (!title.isEmpty()) { + for (const TimelineRecordEntry& record : m_pendingConsoleProfileRecords) { + String recordTitle; + record.data->getString(ASCIILiteral("title"), recordTitle); + if (recordTitle == title) { + if (WebConsoleAgent* consoleAgent = m_instrumentingAgents.webConsoleAgent()) { + // FIXME: Send an enum to the frontend for localization? + String warning = title.isEmpty() ? ASCIILiteral("Unnamed Profile already exists") : makeString("Profile \"", title, "\" already exists"); + consoleAgent->addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Profile, MessageLevel::Warning, warning)); + } + return; + } + } + } + + if (!m_enabled && m_pendingConsoleProfileRecords.isEmpty()) + startProgrammaticCapture(); + + m_pendingConsoleProfileRecords.append(createRecordEntry(TimelineRecordFactory::createConsoleProfileData(title), TimelineRecordType::ConsoleProfile, true, frameFromExecState(exec))); } -void InspectorTimelineAgent::didCancelFrame() +void InspectorTimelineAgent::stopFromConsole(JSC::ExecState*, const String& title) { - m_pendingFrameRecord.clear(); + // Stop profiles in reverse order. If the title is empty, then stop the last profile. + // Otherwise, match the title of the profile to stop. + for (int i = m_pendingConsoleProfileRecords.size() - 1; i >= 0; --i) { + const TimelineRecordEntry& record = m_pendingConsoleProfileRecords[i]; + + String recordTitle; + record.data->getString(ASCIILiteral("title"), recordTitle); + if (title.isEmpty() || recordTitle == title) { + didCompleteRecordEntry(record); + m_pendingConsoleProfileRecords.remove(i); + + if (!m_enabledFromFrontend && m_pendingConsoleProfileRecords.isEmpty()) + stopProgrammaticCapture(); + + return; + } + } + + if (WebConsoleAgent* consoleAgent = m_instrumentingAgents.webConsoleAgent()) { + // FIXME: Send an enum to the frontend for localization? + String warning = title.isEmpty() ? ASCIILiteral("No profiles exist") : makeString("Profile \"", title, "\" does not exist"); + consoleAgent->addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::ProfileEnd, MessageLevel::Warning, warning)); + } } void InspectorTimelineAgent::willCallFunction(const String& scriptName, int scriptLine, Frame* frame) @@ -139,7 +300,7 @@ void InspectorTimelineAgent::willCallFunction(const String& scriptName, int scri pushCurrentRecord(TimelineRecordFactory::createFunctionCallData(scriptName, scriptLine), TimelineRecordType::FunctionCall, true, frame); } -void InspectorTimelineAgent::didCallFunction() +void InspectorTimelineAgent::didCallFunction(Frame*) { didCompleteCurrentRecord(TimelineRecordType::FunctionCall); } @@ -154,37 +315,24 @@ void InspectorTimelineAgent::didDispatchEvent() didCompleteCurrentRecord(TimelineRecordType::EventDispatch); } -void InspectorTimelineAgent::didInvalidateLayout(Frame* frame) +void InspectorTimelineAgent::didInvalidateLayout(Frame& frame) { - appendRecord(InspectorObject::create(), TimelineRecordType::InvalidateLayout, true, frame); + appendRecord(InspectorObject::create(), TimelineRecordType::InvalidateLayout, true, &frame); } -void InspectorTimelineAgent::willLayout(Frame* frame) +void InspectorTimelineAgent::willLayout(Frame& frame) { - RenderObject* root = frame->view()->layoutRoot(); - bool partialLayout = !!root; - - if (!partialLayout) - root = frame->contentRenderer(); - - unsigned dirtyObjects = 0; - unsigned totalObjects = 0; - for (RenderObject* o = root; o; o = o->nextInPreOrder(root)) { - ++totalObjects; - if (o->needsLayout()) - ++dirtyObjects; - } - pushCurrentRecord(TimelineRecordFactory::createLayoutData(dirtyObjects, totalObjects, partialLayout), TimelineRecordType::Layout, true, frame); + pushCurrentRecord(InspectorObject::create(), TimelineRecordType::Layout, true, &frame); } -void InspectorTimelineAgent::didLayout(RenderObject* root) +void InspectorTimelineAgent::didLayout(RenderObject& root) { if (m_recordStack.isEmpty()) return; TimelineRecordEntry& entry = m_recordStack.last(); ASSERT(entry.type == TimelineRecordType::Layout); Vector<FloatQuad> quads; - root->absoluteQuads(quads); + root.absoluteQuads(quads); if (quads.size() >= 1) TimelineRecordFactory::appendLayoutRoot(entry.data.get(), quads[0]); else @@ -207,56 +355,36 @@ void InspectorTimelineAgent::didRecalculateStyle() didCompleteCurrentRecord(TimelineRecordType::RecalculateStyles); } -void InspectorTimelineAgent::willPaint(Frame* frame) +void InspectorTimelineAgent::willComposite(Frame& frame) { - pushCurrentRecord(InspectorObject::create(), TimelineRecordType::Paint, true, frame); -} - -void InspectorTimelineAgent::didPaint(RenderObject* renderer, const LayoutRect& clipRect) -{ - TimelineRecordEntry& entry = m_recordStack.last(); - ASSERT(entry.type == TimelineRecordType::Paint); - FloatQuad quad; - localToPageQuad(*renderer, clipRect, &quad); - entry.data = TimelineRecordFactory::createPaintData(quad); - didCompleteCurrentRecord(TimelineRecordType::Paint); -} - -void InspectorTimelineAgent::willScroll(Frame* frame) -{ - pushCurrentRecord(InspectorObject::create(), TimelineRecordType::ScrollLayer, false, frame); -} - -void InspectorTimelineAgent::didScroll() -{ - didCompleteCurrentRecord(TimelineRecordType::ScrollLayer); -} - -void InspectorTimelineAgent::willComposite() -{ - pushCurrentRecord(InspectorObject::create(), TimelineRecordType::CompositeLayers, false, nullptr); + ASSERT(!m_startedComposite); + pushCurrentRecord(InspectorObject::create(), TimelineRecordType::Composite, true, &frame); + m_startedComposite = true; } void InspectorTimelineAgent::didComposite() { - didCompleteCurrentRecord(TimelineRecordType::CompositeLayers); + ASSERT(m_startedComposite); + didCompleteCurrentRecord(TimelineRecordType::Composite); + m_startedComposite = false; } -void InspectorTimelineAgent::willWriteHTML(unsigned startLine, Frame* frame) +void InspectorTimelineAgent::willPaint(Frame& frame) { - pushCurrentRecord(TimelineRecordFactory::createParseHTMLData(startLine), TimelineRecordType::ParseHTML, true, frame); + pushCurrentRecord(InspectorObject::create(), TimelineRecordType::Paint, true, &frame); } -void InspectorTimelineAgent::didWriteHTML(unsigned endLine) +void InspectorTimelineAgent::didPaint(RenderObject& renderer, const LayoutRect& clipRect) { - if (!m_recordStack.isEmpty()) { - TimelineRecordEntry entry = m_recordStack.last(); - entry.data->setNumber("endLine", endLine); - didCompleteCurrentRecord(TimelineRecordType::ParseHTML); - } + TimelineRecordEntry& entry = m_recordStack.last(); + ASSERT(entry.type == TimelineRecordType::Paint); + FloatQuad quad; + localToPageQuad(renderer, clipRect, &quad); + entry.data = TimelineRecordFactory::createPaintData(quad); + didCompleteCurrentRecord(TimelineRecordType::Paint); } -void InspectorTimelineAgent::didInstallTimer(int timerId, int timeout, bool singleShot, Frame* frame) +void InspectorTimelineAgent::didInstallTimer(int timerId, std::chrono::milliseconds timeout, bool singleShot, Frame* frame) { appendRecord(TimelineRecordFactory::createTimerInstallData(timerId, timeout, singleShot), TimelineRecordType::TimerInstall, true, frame); } @@ -276,99 +404,174 @@ void InspectorTimelineAgent::didFireTimer() didCompleteCurrentRecord(TimelineRecordType::TimerFire); } -void InspectorTimelineAgent::willDispatchXHRReadyStateChangeEvent(const String& url, int readyState, Frame* frame) +void InspectorTimelineAgent::willEvaluateScript(const String& url, int lineNumber, Frame& frame) { - pushCurrentRecord(TimelineRecordFactory::createXHRReadyStateChangeData(url, readyState), TimelineRecordType::XHRReadyStateChange, false, frame); + pushCurrentRecord(TimelineRecordFactory::createEvaluateScriptData(url, lineNumber), TimelineRecordType::EvaluateScript, true, &frame); } -void InspectorTimelineAgent::didDispatchXHRReadyStateChangeEvent() +void InspectorTimelineAgent::didEvaluateScript(Frame&) { - didCompleteCurrentRecord(TimelineRecordType::XHRReadyStateChange); + didCompleteCurrentRecord(TimelineRecordType::EvaluateScript); } -void InspectorTimelineAgent::willDispatchXHRLoadEvent(const String& url, Frame* frame) +void InspectorTimelineAgent::didTimeStamp(Frame& frame, const String& message) { - pushCurrentRecord(TimelineRecordFactory::createXHRLoadData(url), TimelineRecordType::XHRLoad, true, frame); + appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::TimeStamp, true, &frame); } -void InspectorTimelineAgent::didDispatchXHRLoadEvent() +void InspectorTimelineAgent::time(Frame& frame, const String& message) { - didCompleteCurrentRecord(TimelineRecordType::XHRLoad); + appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::Time, true, &frame); } -void InspectorTimelineAgent::willEvaluateScript(const String& url, int lineNumber, Frame* frame) +void InspectorTimelineAgent::timeEnd(Frame& frame, const String& message) { - pushCurrentRecord(TimelineRecordFactory::createEvaluateScriptData(url, lineNumber), TimelineRecordType::EvaluateScript, true, frame); + appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::TimeEnd, true, &frame); } -void InspectorTimelineAgent::didEvaluateScript() +void InspectorTimelineAgent::mainFrameStartedLoading() { - didCompleteCurrentRecord(TimelineRecordType::EvaluateScript); -} + if (m_enabled) + return; -void InspectorTimelineAgent::didScheduleResourceRequest(const String& url, Frame* frame) -{ - appendRecord(TimelineRecordFactory::createScheduleResourceRequestData(url), TimelineRecordType::ScheduleResourceRequest, true, frame); -} + if (!m_autoCaptureEnabled) + return; -void InspectorTimelineAgent::willSendResourceRequest(unsigned long identifier, const ResourceRequest& request, Frame* frame) -{ - String requestId = IdentifiersFactory::requestId(identifier); - appendRecord(TimelineRecordFactory::createResourceSendRequestData(requestId, request), TimelineRecordType::ResourceSendRequest, true, frame); -} + if (m_instruments.isEmpty()) + return; -void InspectorTimelineAgent::willReceiveResourceData(unsigned long identifier, Frame* frame, int length) -{ - String requestId = IdentifiersFactory::requestId(identifier); - pushCurrentRecord(TimelineRecordFactory::createReceiveResourceData(requestId, length), TimelineRecordType::ResourceReceivedData, false, frame); -} + m_autoCapturePhase = AutoCapturePhase::BeforeLoad; -void InspectorTimelineAgent::didReceiveResourceData() -{ - didCompleteCurrentRecord(TimelineRecordType::ResourceReceivedData); + // Pre-emptively disable breakpoints. The frontend must re-enable them. + if (InspectorDebuggerAgent* debuggerAgent = m_instrumentingAgents.inspectorDebuggerAgent()) { + ErrorString unused; + debuggerAgent->setBreakpointsActive(unused, false); + } + + // Inform the frontend we started an auto capture. The frontend must stop capture. + m_frontendDispatcher->autoCaptureStarted(); + + toggleInstruments(InstrumentState::Start); } -void InspectorTimelineAgent::willReceiveResourceResponse(unsigned long identifier, const ResourceResponse& response, Frame* frame) +void InspectorTimelineAgent::mainFrameNavigated() { - String requestId = IdentifiersFactory::requestId(identifier); - pushCurrentRecord(TimelineRecordFactory::createResourceReceiveResponseData(requestId, response), TimelineRecordType::ResourceReceiveResponse, false, frame); + if (m_autoCapturePhase == AutoCapturePhase::BeforeLoad) { + m_autoCapturePhase = AutoCapturePhase::FirstNavigation; + toggleInstruments(InstrumentState::Start); + m_autoCapturePhase = AutoCapturePhase::AfterFirstNavigation; + } } -void InspectorTimelineAgent::didReceiveResourceResponse() +void InspectorTimelineAgent::startProgrammaticCapture() { - didCompleteCurrentRecord(TimelineRecordType::ResourceReceiveResponse); + ASSERT(!m_enabled); + + // Disable breakpoints during programmatic capture. + if (InspectorDebuggerAgent* debuggerAgent = m_instrumentingAgents.inspectorDebuggerAgent()) { + m_programmaticCaptureRestoreBreakpointActiveValue = debuggerAgent->breakpointsActive(); + if (m_programmaticCaptureRestoreBreakpointActiveValue) { + ErrorString unused; + debuggerAgent->setBreakpointsActive(unused, false); + } + } else + m_programmaticCaptureRestoreBreakpointActiveValue = false; + + m_frontendDispatcher->programmaticCaptureStarted(); + + toggleScriptProfilerInstrument(InstrumentState::Start); // Ensure JavaScript samping data. + toggleTimelineInstrument(InstrumentState::Start); // Ensure Console Profile event records. + toggleInstruments(InstrumentState::Start); // Any other instruments the frontend wants us to record. } -void InspectorTimelineAgent::didFinishLoadingResource(unsigned long identifier, bool didFail, double finishTime, Frame* frame) +void InspectorTimelineAgent::stopProgrammaticCapture() { - appendRecord(TimelineRecordFactory::createResourceFinishData(IdentifiersFactory::requestId(identifier), didFail, finishTime * 1000), TimelineRecordType::ResourceFinish, false, frame); + ASSERT(m_enabled); + ASSERT(!m_enabledFromFrontend); + + toggleInstruments(InstrumentState::Stop); + toggleTimelineInstrument(InstrumentState::Stop); + toggleScriptProfilerInstrument(InstrumentState::Stop); + + // Re-enable breakpoints if they were enabled. + if (m_programmaticCaptureRestoreBreakpointActiveValue) { + if (InspectorDebuggerAgent* debuggerAgent = m_instrumentingAgents.inspectorDebuggerAgent()) { + ErrorString unused; + debuggerAgent->setBreakpointsActive(unused, true); + } + } + + m_frontendDispatcher->programmaticCaptureStopped(); } -void InspectorTimelineAgent::didTimeStamp(Frame* frame, const String& message) +void InspectorTimelineAgent::toggleInstruments(InstrumentState state) { - appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::TimeStamp, true, frame); + for (auto instrumentType : m_instruments) { + switch (instrumentType) { + case Inspector::Protocol::Timeline::Instrument::ScriptProfiler: { + toggleScriptProfilerInstrument(state); + break; + } + case Inspector::Protocol::Timeline::Instrument::Heap: { + toggleHeapInstrument(state); + break; + } + case Inspector::Protocol::Timeline::Instrument::Memory: { + toggleMemoryInstrument(state); + break; + } + case Inspector::Protocol::Timeline::Instrument::Timeline: + toggleTimelineInstrument(state); + break; + } + } } -void InspectorTimelineAgent::time(Frame* frame, const String& message) +void InspectorTimelineAgent::toggleScriptProfilerInstrument(InstrumentState state) { - appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::Time, true, frame); + if (m_scriptProfilerAgent) { + ErrorString unused; + if (state == InstrumentState::Start) { + const bool includeSamples = true; + m_scriptProfilerAgent->startTracking(unused, &includeSamples); + } else + m_scriptProfilerAgent->stopTracking(unused); + } } -void InspectorTimelineAgent::timeEnd(Frame* frame, const String& message) +void InspectorTimelineAgent::toggleHeapInstrument(InstrumentState state) { - appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::TimeEnd, true, frame); + if (m_heapAgent) { + ErrorString unused; + if (state == InstrumentState::Start) { + if (m_autoCapturePhase == AutoCapturePhase::None || m_autoCapturePhase == AutoCapturePhase::FirstNavigation) + m_heapAgent->startTracking(unused); + } else + m_heapAgent->stopTracking(unused); + } } -void InspectorTimelineAgent::didMarkDOMContentEvent(Frame* frame) +void InspectorTimelineAgent::toggleMemoryInstrument(InstrumentState state) { - bool isMainFrame = frame && m_pageAgent && (frame == m_pageAgent->mainFrame()); - appendRecord(TimelineRecordFactory::createMarkData(isMainFrame), TimelineRecordType::MarkDOMContent, false, frame); +#if ENABLE(RESOURCE_USAGE) + if (InspectorMemoryAgent* memoryAgent = m_instrumentingAgents.inspectorMemoryAgent()) { + ErrorString unused; + if (state == InstrumentState::Start) + memoryAgent->startTracking(unused); + else + memoryAgent->stopTracking(unused); + } +#else + UNUSED_PARAM(state); +#endif } -void InspectorTimelineAgent::didMarkLoadEvent(Frame* frame) +void InspectorTimelineAgent::toggleTimelineInstrument(InstrumentState state) { - bool isMainFrame = frame && m_pageAgent && (frame == m_pageAgent->mainFrame()); - appendRecord(TimelineRecordFactory::createMarkData(isMainFrame), TimelineRecordType::MarkLoad, false, frame); + if (state == InstrumentState::Start) + internalStart(); + else + internalStop(); } void InspectorTimelineAgent::didCommitLoad() @@ -396,160 +599,83 @@ void InspectorTimelineAgent::didFireAnimationFrame() didCompleteCurrentRecord(TimelineRecordType::FireAnimationFrame); } -#if ENABLE(WEB_SOCKETS) -void InspectorTimelineAgent::didCreateWebSocket(unsigned long identifier, const URL& url, const String& protocol, Frame* frame) -{ - appendRecord(TimelineRecordFactory::createWebSocketCreateData(identifier, url, protocol), TimelineRecordType::WebSocketCreate, true, frame); -} +// ScriptDebugListener -void InspectorTimelineAgent::willSendWebSocketHandshakeRequest(unsigned long identifier, Frame* frame) +void InspectorTimelineAgent::breakpointActionProbe(JSC::ExecState& state, const Inspector::ScriptBreakpointAction& action, unsigned /*batchId*/, unsigned sampleId, JSC::JSValue) { - appendRecord(TimelineRecordFactory::createGenericWebSocketData(identifier), TimelineRecordType::WebSocketSendHandshakeRequest, true, frame); + appendRecord(TimelineRecordFactory::createProbeSampleData(action, sampleId), TimelineRecordType::ProbeSample, false, frameFromExecState(&state)); } -void InspectorTimelineAgent::didReceiveWebSocketHandshakeResponse(unsigned long identifier, Frame* frame) -{ - appendRecord(TimelineRecordFactory::createGenericWebSocketData(identifier), TimelineRecordType::WebSocketReceiveHandshakeResponse, false, frame); -} - -void InspectorTimelineAgent::didDestroyWebSocket(unsigned long identifier, Frame* frame) -{ - appendRecord(TimelineRecordFactory::createGenericWebSocketData(identifier), TimelineRecordType::WebSocketDestroy, true, frame); -} -#endif // ENABLE(WEB_SOCKETS) - -void InspectorTimelineAgent::addRecordToTimeline(PassRefPtr<InspectorObject> record, TimelineRecordType type) -{ - commitFrameRecord(); - innerAddRecordToTimeline(record, type); -} - -static Inspector::TypeBuilder::Timeline::EventType::Enum toProtocol(TimelineRecordType type) +static Inspector::Protocol::Timeline::EventType toProtocol(TimelineRecordType type) { switch (type) { case TimelineRecordType::EventDispatch: - return Inspector::TypeBuilder::Timeline::EventType::EventDispatch; - case TimelineRecordType::BeginFrame: - return Inspector::TypeBuilder::Timeline::EventType::BeginFrame; + return Inspector::Protocol::Timeline::EventType::EventDispatch; case TimelineRecordType::ScheduleStyleRecalculation: - return Inspector::TypeBuilder::Timeline::EventType::ScheduleStyleRecalculation; + return Inspector::Protocol::Timeline::EventType::ScheduleStyleRecalculation; case TimelineRecordType::RecalculateStyles: - return Inspector::TypeBuilder::Timeline::EventType::RecalculateStyles; + return Inspector::Protocol::Timeline::EventType::RecalculateStyles; case TimelineRecordType::InvalidateLayout: - return Inspector::TypeBuilder::Timeline::EventType::InvalidateLayout; + return Inspector::Protocol::Timeline::EventType::InvalidateLayout; case TimelineRecordType::Layout: - return Inspector::TypeBuilder::Timeline::EventType::Layout; + return Inspector::Protocol::Timeline::EventType::Layout; case TimelineRecordType::Paint: - return Inspector::TypeBuilder::Timeline::EventType::Paint; - case TimelineRecordType::ScrollLayer: - return Inspector::TypeBuilder::Timeline::EventType::ScrollLayer; - case TimelineRecordType::ResizeImage: - return Inspector::TypeBuilder::Timeline::EventType::ResizeImage; - case TimelineRecordType::CompositeLayers: - return Inspector::TypeBuilder::Timeline::EventType::CompositeLayers; - - case TimelineRecordType::ParseHTML: - return Inspector::TypeBuilder::Timeline::EventType::ParseHTML; + return Inspector::Protocol::Timeline::EventType::Paint; + case TimelineRecordType::Composite: + return Inspector::Protocol::Timeline::EventType::Composite; + case TimelineRecordType::RenderingFrame: + return Inspector::Protocol::Timeline::EventType::RenderingFrame; case TimelineRecordType::TimerInstall: - return Inspector::TypeBuilder::Timeline::EventType::TimerInstall; + return Inspector::Protocol::Timeline::EventType::TimerInstall; case TimelineRecordType::TimerRemove: - return Inspector::TypeBuilder::Timeline::EventType::TimerRemove; + return Inspector::Protocol::Timeline::EventType::TimerRemove; case TimelineRecordType::TimerFire: - return Inspector::TypeBuilder::Timeline::EventType::TimerFire; + return Inspector::Protocol::Timeline::EventType::TimerFire; case TimelineRecordType::EvaluateScript: - return Inspector::TypeBuilder::Timeline::EventType::EvaluateScript; - - case TimelineRecordType::MarkLoad: - return Inspector::TypeBuilder::Timeline::EventType::MarkLoad; - case TimelineRecordType::MarkDOMContent: - return Inspector::TypeBuilder::Timeline::EventType::MarkDOMContent; + return Inspector::Protocol::Timeline::EventType::EvaluateScript; case TimelineRecordType::TimeStamp: - return Inspector::TypeBuilder::Timeline::EventType::TimeStamp; + return Inspector::Protocol::Timeline::EventType::TimeStamp; case TimelineRecordType::Time: - return Inspector::TypeBuilder::Timeline::EventType::Time; + return Inspector::Protocol::Timeline::EventType::Time; case TimelineRecordType::TimeEnd: - return Inspector::TypeBuilder::Timeline::EventType::TimeEnd; - - case TimelineRecordType::ScheduleResourceRequest: - return Inspector::TypeBuilder::Timeline::EventType::ScheduleResourceRequest; - case TimelineRecordType::ResourceSendRequest: - return Inspector::TypeBuilder::Timeline::EventType::ResourceSendRequest; - case TimelineRecordType::ResourceReceiveResponse: - return Inspector::TypeBuilder::Timeline::EventType::ResourceReceiveResponse; - case TimelineRecordType::ResourceReceivedData: - return Inspector::TypeBuilder::Timeline::EventType::ResourceReceivedData; - case TimelineRecordType::ResourceFinish: - return Inspector::TypeBuilder::Timeline::EventType::ResourceFinish; - - case TimelineRecordType::XHRReadyStateChange: - return Inspector::TypeBuilder::Timeline::EventType::XHRReadyStateChange; - case TimelineRecordType::XHRLoad: - return Inspector::TypeBuilder::Timeline::EventType::XHRLoad; + return Inspector::Protocol::Timeline::EventType::TimeEnd; case TimelineRecordType::FunctionCall: - return Inspector::TypeBuilder::Timeline::EventType::FunctionCall; + return Inspector::Protocol::Timeline::EventType::FunctionCall; + case TimelineRecordType::ProbeSample: + return Inspector::Protocol::Timeline::EventType::ProbeSample; + case TimelineRecordType::ConsoleProfile: + return Inspector::Protocol::Timeline::EventType::ConsoleProfile; case TimelineRecordType::RequestAnimationFrame: - return Inspector::TypeBuilder::Timeline::EventType::RequestAnimationFrame; + return Inspector::Protocol::Timeline::EventType::RequestAnimationFrame; case TimelineRecordType::CancelAnimationFrame: - return Inspector::TypeBuilder::Timeline::EventType::CancelAnimationFrame; + return Inspector::Protocol::Timeline::EventType::CancelAnimationFrame; case TimelineRecordType::FireAnimationFrame: - return Inspector::TypeBuilder::Timeline::EventType::FireAnimationFrame; - - case TimelineRecordType::WebSocketCreate: - return Inspector::TypeBuilder::Timeline::EventType::WebSocketCreate; - case TimelineRecordType::WebSocketSendHandshakeRequest: - return Inspector::TypeBuilder::Timeline::EventType::WebSocketSendHandshakeRequest; - case TimelineRecordType::WebSocketReceiveHandshakeResponse: - return Inspector::TypeBuilder::Timeline::EventType::WebSocketReceiveHandshakeResponse; - case TimelineRecordType::WebSocketDestroy: - return Inspector::TypeBuilder::Timeline::EventType::WebSocketDestroy; + return Inspector::Protocol::Timeline::EventType::FireAnimationFrame; } - return Inspector::TypeBuilder::Timeline::EventType::TimeStamp; + return Inspector::Protocol::Timeline::EventType::TimeStamp; } -void InspectorTimelineAgent::innerAddRecordToTimeline(PassRefPtr<InspectorObject> prpRecord, TimelineRecordType type) +void InspectorTimelineAgent::addRecordToTimeline(RefPtr<InspectorObject>&& record, TimelineRecordType type) { - prpRecord->setString("type", Inspector::TypeBuilder::getWebEnumConstantValue(toProtocol(type))); + ASSERT_ARG(record, record); + record->setString("type", Inspector::Protocol::InspectorHelpers::getEnumConstantValue(toProtocol(type))); - RefPtr<Inspector::TypeBuilder::Timeline::TimelineEvent> record = Inspector::TypeBuilder::Timeline::TimelineEvent::runtimeCast(prpRecord); + if (m_recordStack.isEmpty()) { + auto recordObject = BindingTraits<Inspector::Protocol::Timeline::TimelineEvent>::runtimeCast(WTFMove(record)); + sendEvent(WTFMove(recordObject)); + } else { + const TimelineRecordEntry& parent = m_recordStack.last(); + // Nested paint records are an implementation detail and add no information not already contained in the parent. + if (type == TimelineRecordType::Paint && parent.type == type) + return; - setDOMCounters(record.get()); - - if (m_recordStack.isEmpty()) - sendEvent(record.release()); - else { - TimelineRecordEntry parent = m_recordStack.last(); - parent.children->pushObject(record.release()); - } -} - -static size_t usedHeapSize() -{ - return JSDOMWindow::commonVM()->heap.size(); -} - -void InspectorTimelineAgent::setDOMCounters(Inspector::TypeBuilder::Timeline::TimelineEvent* record) -{ - record->setUsedHeapSize(usedHeapSize()); - - if (m_includeDOMCounters) { - int documentCount = 0; - int nodeCount = 0; - if (m_inspectorType == PageInspector) { - documentCount = InspectorCounters::counterValue(InspectorCounters::DocumentCounter); - nodeCount = InspectorCounters::counterValue(InspectorCounters::NodeCounter); - } - int listenerCount = ThreadLocalInspectorCounters::current().counterValue(ThreadLocalInspectorCounters::JSEventListenerCounter); - RefPtr<Inspector::TypeBuilder::Timeline::DOMCounters> counters = Inspector::TypeBuilder::Timeline::DOMCounters::create() - .setDocuments(documentCount) - .setNodes(nodeCount) - .setJsEventListeners(listenerCount); - record->setCounters(counters.release()); + parent.children->pushObject(WTFMove(record)); } } @@ -563,6 +689,14 @@ void InspectorTimelineAgent::setFrameIdentifier(InspectorObject* record, Frame* record->setString("frameId", frameId); } +void InspectorTimelineAgent::didCompleteRecordEntry(const TimelineRecordEntry& entry) +{ + entry.record->setObject(ASCIILiteral("data"), entry.data); + entry.record->setArray(ASCIILiteral("children"), entry.children); + entry.record->setDouble(ASCIILiteral("endTime"), timestamp()); + addRecordToTimeline(entry.record.copyRef(), entry.type); +} + void InspectorTimelineAgent::didCompleteCurrentRecord(TimelineRecordType type) { // An empty stack could merely mean that the timeline agent was turned on in the middle of @@ -570,66 +704,45 @@ void InspectorTimelineAgent::didCompleteCurrentRecord(TimelineRecordType type) if (!m_recordStack.isEmpty()) { TimelineRecordEntry entry = m_recordStack.last(); m_recordStack.removeLast(); - ASSERT(entry.type == type); - entry.record->setObject("data", entry.data); - entry.record->setArray("children", entry.children); - entry.record->setNumber("endTime", timestamp()); - size_t usedHeapSizeDelta = usedHeapSize() - entry.usedHeapSizeAtStart; - if (usedHeapSizeDelta) - entry.record->setNumber("usedHeapSizeDelta", usedHeapSizeDelta); - addRecordToTimeline(entry.record, type); - } -} + ASSERT_UNUSED(type, entry.type == type); -InspectorTimelineAgent::InspectorTimelineAgent(InstrumentingAgents* instrumentingAgents, InspectorPageAgent* pageAgent, InspectorMemoryAgent* memoryAgent, InspectorType type, InspectorClient* client) - : InspectorAgentBase(ASCIILiteral("Timeline"), instrumentingAgents) - , m_pageAgent(pageAgent) - , m_memoryAgent(memoryAgent) - , m_id(1) - , m_maxCallStackDepth(5) - , m_inspectorType(type) - , m_client(client) - , m_weakFactory(this) - , m_enabled(false) - , m_includeDOMCounters(false) -{ + // Don't send RenderingFrame records that have no children to reduce noise. + if (entry.type == TimelineRecordType::RenderingFrame && !entry.children->length()) + return; + + didCompleteRecordEntry(entry); + } } -void InspectorTimelineAgent::appendRecord(PassRefPtr<InspectorObject> data, TimelineRecordType type, bool captureCallStack, Frame* frame) +void InspectorTimelineAgent::appendRecord(RefPtr<InspectorObject>&& data, TimelineRecordType type, bool captureCallStack, Frame* frame) { - RefPtr<InspectorObject> record = TimelineRecordFactory::createGenericRecord(timestamp(), captureCallStack ? m_maxCallStackDepth : 0); - record->setObject("data", data); - setFrameIdentifier(record.get(), frame); - addRecordToTimeline(record.release(), type); + Ref<InspectorObject> record = TimelineRecordFactory::createGenericRecord(timestamp(), captureCallStack ? m_maxCallStackDepth : 0); + record->setObject("data", WTFMove(data)); + setFrameIdentifier(&record.get(), frame); + addRecordToTimeline(WTFMove(record), type); } -void InspectorTimelineAgent::sendEvent(PassRefPtr<InspectorObject> event) +void InspectorTimelineAgent::sendEvent(RefPtr<InspectorObject>&& event) { // FIXME: runtimeCast is a hack. We do it because we can't build TimelineEvent directly now. - RefPtr<Inspector::TypeBuilder::Timeline::TimelineEvent> recordChecked = Inspector::TypeBuilder::Timeline::TimelineEvent::runtimeCast(event); - m_frontendDispatcher->eventRecorded(recordChecked.release()); + auto recordChecked = BindingTraits<Inspector::Protocol::Timeline::TimelineEvent>::runtimeCast(WTFMove(event)); + m_frontendDispatcher->eventRecorded(WTFMove(recordChecked)); } -void InspectorTimelineAgent::pushCurrentRecord(PassRefPtr<InspectorObject> data, TimelineRecordType type, bool captureCallStack, Frame* frame) +InspectorTimelineAgent::TimelineRecordEntry InspectorTimelineAgent::createRecordEntry(RefPtr<InspectorObject>&& data, TimelineRecordType type, bool captureCallStack, Frame* frame) { - commitFrameRecord(); - RefPtr<InspectorObject> record = TimelineRecordFactory::createGenericRecord(timestamp(), captureCallStack ? m_maxCallStackDepth : 0); - setFrameIdentifier(record.get(), frame); - m_recordStack.append(TimelineRecordEntry(record.release(), data, InspectorArray::create(), type, usedHeapSize())); + Ref<InspectorObject> record = TimelineRecordFactory::createGenericRecord(timestamp(), captureCallStack ? m_maxCallStackDepth : 0); + setFrameIdentifier(&record.get(), frame); + return TimelineRecordEntry(WTFMove(record), WTFMove(data), InspectorArray::create(), type); } -void InspectorTimelineAgent::commitFrameRecord() +void InspectorTimelineAgent::pushCurrentRecord(RefPtr<InspectorObject>&& data, TimelineRecordType type, bool captureCallStack, Frame* frame) { - if (!m_pendingFrameRecord) - return; - - m_pendingFrameRecord->setObject("data", InspectorObject::create()); - innerAddRecordToTimeline(m_pendingFrameRecord.release(), TimelineRecordType::BeginFrame); + pushCurrentRecord(createRecordEntry(WTFMove(data), type, captureCallStack, frame)); } void InspectorTimelineAgent::clearRecordStack() { - m_pendingFrameRecord.clear(); m_recordStack.clear(); m_id++; } @@ -644,16 +757,4 @@ void InspectorTimelineAgent::localToPageQuad(const RenderObject& renderer, const quad->setP4(frameView.contentsToRootView(roundedIntPoint(absolute.p4()))); } -double InspectorTimelineAgent::timestamp() -{ - return m_timeConverter.fromMonotonicallyIncreasingTime(monotonicallyIncreasingTime()); -} - -Page* InspectorTimelineAgent::page() -{ - return m_pageAgent ? m_pageAgent->page() : nullptr; -} - } // namespace WebCore - -#endif // ENABLE(INSPECTOR) |