Web Inspector: timelines should not count time elapsed while paused in the debugger
authorburg@cs.washington.edu <burg@cs.washington.edu@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 25 Oct 2014 22:05:57 +0000 (22:05 +0000)
committerburg@cs.washington.edu <burg@cs.washington.edu@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 25 Oct 2014 22:05:57 +0000 (22:05 +0000)
https://bugs.webkit.org/show_bug.cgi?id=136351

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

Now that we have a stopwatch to provide pause-aware timing data, we can remove the
profiler's handling of debugger pause/continue callbacks. The debugger agent accounts
for suspended execution by pausing and resuming the stopwatch.

* API/JSProfilerPrivate.cpp:
(JSStartProfiling): Use a fresh stopwatch when profiling from the JSC API.
* inspector/InspectorEnvironment.h:
* inspector/JSGlobalObjectInspectorController.cpp:
(Inspector::JSGlobalObjectInspectorController::JSGlobalObjectInspectorController):
(Inspector::JSGlobalObjectInspectorController::executionStopwatch):
* inspector/JSGlobalObjectInspectorController.h:
* inspector/ScriptDebugServer.cpp:
(Inspector::ScriptDebugServer::handlePause):
* inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::didPause):
(Inspector::InspectorDebuggerAgent::breakpointActionProbe):
(Inspector::InspectorDebuggerAgent::didContinue):
* inspector/agents/InspectorDebuggerAgent.h:
* profiler/LegacyProfiler.cpp:
(JSC::LegacyProfiler::profiler): Use nullptr.
(JSC::LegacyProfiler::startProfiling): Hand off a stopwatch to the profile generator.
(JSC::LegacyProfiler::stopProfiling): Use nullptr.
(JSC::LegacyProfiler::didPause): Deleted.
(JSC::LegacyProfiler::didContinue): Deleted.
* profiler/LegacyProfiler.h:
* profiler/Profile.cpp: The root node should always have a start time of 0.0.
(JSC::Profile::Profile):
* profiler/ProfileGenerator.cpp: Remove debugger pause/continue callbacks and the
timestamp member that was used to track time elapsed by the debugger. Just use the
stopwatch's elapsed times to generate start/elapsed times for function calls.

(JSC::ProfileGenerator::create):
(JSC::ProfileGenerator::ProfileGenerator):
(JSC::AddParentForConsoleStartFunctor::operator()): The parent node of |console.profile|
should have a start time of 0.0, since it represents the starting node of profiling.

(JSC::ProfileGenerator::beginCallEntry):
(JSC::ProfileGenerator::endCallEntry):
(JSC::ProfileGenerator::didPause): Deleted.
(JSC::ProfileGenerator::didContinue): Deleted.
* profiler/ProfileGenerator.h:

Source/WebCore:

To avoid counting time elapsed while the debugger is paused, timeline records should
keep track of time elapsed since the start of timeline capturing, rather than wall clock
timestamps. We can easily compute elapsed time by sharing a Stopwatch instance through the
inspector environment. The stopwatch runs with timelines and is paused with the debugger,
so subsequent time measurements will not include time elapsed while the debugger is paused.

This refactoring is safe because start and end times are only used to graph records; the
timestamp's actual value is irrelevant and is not displayed in the user interface. Date
timestamps are still included with network-related records as part of their header data.

No new tests, because we cannot reliably test timing changes induced by debugger pauses.
It is possible for records to accrue time before the debugger pauses or after it resumes.

* inspector/InspectorCSSAgent.cpp: Remove unnecessary include.
* inspector/InspectorController.cpp:
(WebCore::InspectorController::InspectorController):
(WebCore::InspectorController::executionStopwatch): Add a shared stopwatch.
* inspector/InspectorController.h:
* inspector/InspectorPageAgent.cpp:
(WebCore::InspectorPageAgent::timestamp): Redirect to the shared stopwatch.
(WebCore::InspectorPageAgent::domContentEventFired):
(WebCore::InspectorPageAgent::loadEventFired):
* inspector/InspectorPageAgent.h:
* inspector/InspectorResourceAgent.cpp:
(WebCore::InspectorResourceAgent::timestamp): Redirect to the shared stopwatch.
(WebCore::InspectorResourceAgent::willSendRequest):
(WebCore::InspectorResourceAgent::didReceiveResponse):
(WebCore::InspectorResourceAgent::didReceiveData):
(WebCore::InspectorResourceAgent::didFinishLoading):
(WebCore::InspectorResourceAgent::didFailLoading):
(WebCore::InspectorResourceAgent::didLoadResourceFromMemoryCache):
(WebCore::InspectorResourceAgent::willSendWebSocketHandshakeRequest):
(WebCore::InspectorResourceAgent::didReceiveWebSocketHandshakeResponse):
(WebCore::InspectorResourceAgent::didCloseWebSocket):
(WebCore::InspectorResourceAgent::didReceiveWebSocketFrame):
(WebCore::InspectorResourceAgent::didSendWebSocketFrame):
(WebCore::InspectorResourceAgent::didReceiveWebSocketFrameError):
* inspector/InspectorResourceAgent.h:
* inspector/InspectorTimelineAgent.cpp:
(WebCore::InspectorTimelineAgent::internalStart): Start and stop the stopwatch with timelines.
(WebCore::InspectorTimelineAgent::internalStop):
(WebCore::InspectorTimelineAgent::timestamp): Redirect to the shared stopwatch.
(WebCore::startProfiling):
(WebCore::InspectorTimelineAgent::startFromConsole):
(WebCore::InspectorTimelineAgent::willCallFunction):
(WebCore::InspectorTimelineAgent::willEvaluateScript):
(WebCore::TimelineTimeConverter::reset): Deleted.
* inspector/InspectorTimelineAgent.h:
(WebCore::TimelineTimeConverter::TimelineTimeConverter): Deleted.
(WebCore::TimelineTimeConverter::fromMonotonicallyIncreasingTime): Deleted.
(WebCore::InspectorTimelineAgent::timeConverter): Deleted.
* inspector/TimelineRecordFactory.cpp:
* inspector/WorkerInspectorController.cpp:
(WebCore::WorkerInspectorController::WorkerInspectorController):
(WebCore::WorkerInspectorController::executionStopwatch): Add a shared stopwatch.
* inspector/WorkerInspectorController.h:

Source/WebInspectorUI:

Don't update the timeline's current time when the debugger is paused.

Start and end times for timeline records are now in seconds elapsed since timeline
recording started, rather than milliseconds since the epoch. Also convert code that
tracks page/resource load timings to use elapsed times rather than timestamps.

Add a workaround to preserve compatibility with old backends. Convert legacy timestamps
in multiple agents to elapsed times.

* UserInterface/Controllers/FrameResourceManager.js:
(WebInspector.FrameResourceManager.prototype.resourceRequestWillBeSent):
(WebInspector.FrameResourceManager.prototype.resourceRequestWasServedFromMemoryCache):
(WebInspector.FrameResourceManager.prototype.resourceRequestDidReceiveResponse):
(WebInspector.FrameResourceManager.prototype.resourceRequestDidReceiveData):
(WebInspector.FrameResourceManager.prototype.resourceRequestDidFinishLoading):
(WebInspector.FrameResourceManager.prototype.resourceRequestDidFailLoading):
(WebInspector.FrameResourceManager.prototype._addNewResourceToFrame):
* UserInterface/Controllers/ProbeManager.js:
* UserInterface/Controllers/TimelineManager.js:
(WebInspector.TimelineManager.prototype.computeElapsedTime): Forward to the active TimelineRecording.
(WebInspector.TimelineManager.prototype.eventRecorded.processRecord):
(WebInspector.TimelineManager.prototype.eventRecorded):
(WebInspector.TimelineManager.prototype.pageDidLoad):
(WebInspector.TimelineManager.prototype._loadNewRecording):
* UserInterface/Models/Probe.js:
(WebInspector.ProbeSample):
* UserInterface/Models/Resource.js:
(WebInspector.Resource.prototype.updateForRedirectResponse):
(WebInspector.Resource.prototype.updateForResponse):
(WebInspector.Resource.prototype.increaseSize):
(WebInspector.Resource.prototype.markAsFinished):
(WebInspector.Resource.prototype.markAsFailed):
(WebInspector.Resource.prototype.revertMarkAsFinished):
* UserInterface/Models/TimelineRecording.js:
(WebInspector.TimelineRecording.prototype.computeElapsedTime):
* UserInterface/Views/TimelineContentView.js:
(WebInspector.TimelineContentView.prototype._debuggerPaused):
(WebInspector.TimelineContentView.prototype._debuggerResumed):

Source/WTF:

* WTF.vcxproj/WTF.vcxproj:
* WTF.vcxproj/WTF.vcxproj.filters:
* WTF.xcodeproj/project.pbxproj:
* wtf/CMakeLists.txt:
* wtf/Stopwatch.h: Added. This implements a refcounted monotonic stopwatch.
(WTF::Stopwatch::create):
(WTF::Stopwatch::Stopwatch):
(WTF::Stopwatch::reset):
(WTF::Stopwatch::start):
(WTF::Stopwatch::stop):
(WTF::Stopwatch::elapsedTime):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@175203 268f45cc-cd09-0410-ab3c-d52691b4dbfc

40 files changed:
Source/JavaScriptCore/API/JSProfilerPrivate.cpp
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/InspectorEnvironment.h
Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp
Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.h
Source/JavaScriptCore/inspector/ScriptDebugServer.cpp
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h
Source/JavaScriptCore/profiler/LegacyProfiler.cpp
Source/JavaScriptCore/profiler/LegacyProfiler.h
Source/JavaScriptCore/profiler/Profile.cpp
Source/JavaScriptCore/profiler/ProfileGenerator.cpp
Source/JavaScriptCore/profiler/ProfileGenerator.h
Source/WTF/ChangeLog
Source/WTF/WTF.vcxproj/WTF.vcxproj
Source/WTF/WTF.vcxproj/WTF.vcxproj.filters
Source/WTF/WTF.xcodeproj/project.pbxproj
Source/WTF/wtf/CMakeLists.txt
Source/WTF/wtf/Stopwatch.h [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/inspector/InspectorCSSAgent.cpp
Source/WebCore/inspector/InspectorController.cpp
Source/WebCore/inspector/InspectorController.h
Source/WebCore/inspector/InspectorPageAgent.cpp
Source/WebCore/inspector/InspectorPageAgent.h
Source/WebCore/inspector/InspectorResourceAgent.cpp
Source/WebCore/inspector/InspectorResourceAgent.h
Source/WebCore/inspector/InspectorTimelineAgent.cpp
Source/WebCore/inspector/InspectorTimelineAgent.h
Source/WebCore/inspector/TimelineRecordFactory.cpp
Source/WebCore/inspector/WorkerInspectorController.cpp
Source/WebCore/inspector/WorkerInspectorController.h
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js
Source/WebInspectorUI/UserInterface/Controllers/ProbeManager.js
Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
Source/WebInspectorUI/UserInterface/Models/Probe.js
Source/WebInspectorUI/UserInterface/Models/Resource.js
Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js
Source/WebInspectorUI/UserInterface/Views/TimelineContentView.js

index 2a5ec2c823901ed9e1bbd824cfa9c63f8a823686..ac112ae6effcc5662aec20f4c8f400d4647d30a7 100644 (file)
@@ -34,7 +34,11 @@ using namespace JSC;
 
 void JSStartProfiling(JSContextRef ctx, JSStringRef title)
 {
-    LegacyProfiler::profiler()->startProfiling(toJS(ctx), title->string());
+    // Use an independent stopwatch for API-initiated profiling, since the user will expect it
+    // to be relative to when their command was issued.
+    RefPtr<Stopwatch> stopwatch = Stopwatch::create();
+    stopwatch->start();
+    LegacyProfiler::profiler()->startProfiling(toJS(ctx), title->string(), stopwatch.release());
 }
 
 void JSEndProfiling(JSContextRef ctx, JSStringRef title)
index bb1ecf629700733bc509eeb260dd611d0fcc8290..89048e35e2a46cd4660f7434aa66b36ff482bc87 100644 (file)
@@ -1,3 +1,52 @@
+2014-10-18  Brian J. Burg  <burg@cs.washington.edu>
+
+        Web Inspector: timelines should not count time elapsed while paused in the debugger
+        https://bugs.webkit.org/show_bug.cgi?id=136351
+
+        Reviewed by Timothy Hatcher.
+
+        Now that we have a stopwatch to provide pause-aware timing data, we can remove the
+        profiler's handling of debugger pause/continue callbacks. The debugger agent accounts
+        for suspended execution by pausing and resuming the stopwatch.
+
+        * API/JSProfilerPrivate.cpp:
+        (JSStartProfiling): Use a fresh stopwatch when profiling from the JSC API.
+        * inspector/InspectorEnvironment.h:
+        * inspector/JSGlobalObjectInspectorController.cpp:
+        (Inspector::JSGlobalObjectInspectorController::JSGlobalObjectInspectorController):
+        (Inspector::JSGlobalObjectInspectorController::executionStopwatch):
+        * inspector/JSGlobalObjectInspectorController.h:
+        * inspector/ScriptDebugServer.cpp:
+        (Inspector::ScriptDebugServer::handlePause):
+        * inspector/agents/InspectorDebuggerAgent.cpp:
+        (Inspector::InspectorDebuggerAgent::didPause):
+        (Inspector::InspectorDebuggerAgent::breakpointActionProbe):
+        (Inspector::InspectorDebuggerAgent::didContinue):
+        * inspector/agents/InspectorDebuggerAgent.h:
+        * profiler/LegacyProfiler.cpp:
+        (JSC::LegacyProfiler::profiler): Use nullptr.
+        (JSC::LegacyProfiler::startProfiling): Hand off a stopwatch to the profile generator.
+        (JSC::LegacyProfiler::stopProfiling): Use nullptr.
+        (JSC::LegacyProfiler::didPause): Deleted.
+        (JSC::LegacyProfiler::didContinue): Deleted.
+        * profiler/LegacyProfiler.h:
+        * profiler/Profile.cpp: The root node should always have a start time of 0.0.
+        (JSC::Profile::Profile):
+        * profiler/ProfileGenerator.cpp: Remove debugger pause/continue callbacks and the
+        timestamp member that was used to track time elapsed by the debugger. Just use the
+        stopwatch's elapsed times to generate start/elapsed times for function calls.
+
+        (JSC::ProfileGenerator::create):
+        (JSC::ProfileGenerator::ProfileGenerator):
+        (JSC::AddParentForConsoleStartFunctor::operator()): The parent node of |console.profile|
+        should have a start time of 0.0, since it represents the starting node of profiling.
+
+        (JSC::ProfileGenerator::beginCallEntry):
+        (JSC::ProfileGenerator::endCallEntry):
+        (JSC::ProfileGenerator::didPause): Deleted.
+        (JSC::ProfileGenerator::didContinue): Deleted.
+        * profiler/ProfileGenerator.h:
+
 2014-10-24  Mark Lam  <mark.lam@apple.com>
 
         Simplified IndexingType's hasAnyArrayStorage().
index 6e43332dc50ceb9643543953da7f3a79ac875305..ae2a7cd9230bbdcb278581d9f3a90a7c65725fc4 100644 (file)
 
 #include "CallData.h"
 
+namespace WTF {
+class Stopwatch;
+}
+
 namespace JSC {
 class SourceCode;
 }
@@ -47,6 +51,7 @@ public:
     virtual void willCallInjectedScriptFunction(JSC::ExecState*, const String& scriptName, int scriptLine) = 0;
     virtual void didCallInjectedScriptFunction(JSC::ExecState*) = 0;
     virtual void frontendInitialized() = 0;
+    virtual PassRefPtr<WTF::Stopwatch> executionStopwatch() = 0;
 };
 
 } // namespace Inspector
index 9f5eb6fc6af7f7534ab841a7195e38e3ad3d5e9c..48d3fb9fb648267f3f8b21b87c240e227773667d 100644 (file)
@@ -43,6 +43,8 @@
 #include "ScriptArguments.h"
 #include "ScriptCallStack.h"
 #include "ScriptCallStackFactory.h"
+#include <wtf/Stopwatch.h>
+
 #include <cxxabi.h>
 #include <dlfcn.h>
 #include <execinfo.h>
@@ -59,6 +61,7 @@ JSGlobalObjectInspectorController::JSGlobalObjectInspectorController(JSGlobalObj
     : m_globalObject(globalObject)
     , m_injectedScriptManager(std::make_unique<InjectedScriptManager>(*this, InjectedScriptHost::create()))
     , m_inspectorFrontendChannel(nullptr)
+    , m_executionStopwatch(Stopwatch::create())
     , m_includeNativeCallStackWithExceptions(true)
     , m_isAutomaticInspection(false)
 #if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
@@ -79,6 +82,8 @@ JSGlobalObjectInspectorController::JSGlobalObjectInspectorController(JSGlobalObj
     m_agents.append(WTF::move(runtimeAgent));
     m_agents.append(WTF::move(consoleAgent));
     m_agents.append(WTF::move(debuggerAgent));
+
+    m_executionStopwatch->start();
 }
 
 JSGlobalObjectInspectorController::~JSGlobalObjectInspectorController()
@@ -213,6 +218,11 @@ void JSGlobalObjectInspectorController::frontendInitialized()
 #endif
 }
 
+PassRefPtr<Stopwatch> JSGlobalObjectInspectorController::executionStopwatch()
+{
+    return m_executionStopwatch;
+}
+
 } // namespace Inspector
 
 #endif // ENABLE(INSPECTOR)
index c25e678aa12aeb7509c722985ac434d5e766eb33..184c614d8d3fa98b649dc803ac0dc805a7fa5941 100644 (file)
 #include "AugmentableInspectorController.h"
 #endif
 
+namespace WTF {
+class Stopwatch;
+}
+
+
 namespace JSC {
 class ConsoleClient;
 class ExecState;
@@ -89,6 +94,7 @@ public:
     virtual void willCallInjectedScriptFunction(JSC::ExecState*, const String&, int) override { }
     virtual void didCallInjectedScriptFunction(JSC::ExecState*) override { }
     virtual void frontendInitialized() override;
+    virtual PassRefPtr<WTF::Stopwatch> executionStopwatch() override;
 
 #if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
     virtual AugmentableInspectorControllerClient* augmentableInspectorControllerClient() const override { return m_augmentingClient; } 
@@ -109,6 +115,7 @@ private:
     InspectorAgentRegistry m_agents;
     InspectorFrontendChannel* m_inspectorFrontendChannel;
     RefPtr<InspectorBackendDispatcher> m_inspectorBackendDispatcher;
+    RefPtr<WTF::Stopwatch> m_executionStopwatch;
     bool m_includeNativeCallStackWithExceptions;
     bool m_isAutomaticInspection;
 
index 2c783af4776d6154f0ff32346075f7184b2e0b49..3a61663e36c477c1173b2cc388793d152cbf38cd 100644 (file)
@@ -38,7 +38,6 @@
 #include "JSJavaScriptCallFrame.h"
 #include "JSLock.h"
 #include "JavaScriptCallFrame.h"
-#include "LegacyProfiler.h"
 #include "ScriptValue.h"
 #include "SourceProvider.h"
 #include <wtf/NeverDestroyed.h>
@@ -307,14 +306,12 @@ void ScriptDebugServer::handleExceptionInBreakpointCondition(JSC::ExecState* exe
 void ScriptDebugServer::handlePause(Debugger::ReasonForPause, JSGlobalObject* vmEntryGlobalObject)
 {
     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause);
-    LegacyProfiler::profiler()->didPause(currentDebuggerCallFrame());
     didPause(vmEntryGlobalObject);
 
     m_doneProcessingDebuggerEvents = false;
     runEventLoopWhilePaused();
 
     didContinue(vmEntryGlobalObject);
-    LegacyProfiler::profiler()->didContinue(currentDebuggerCallFrame());
     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue);
 }
 
index 02da154c4065f1d47cddc4b5e5fccc31937501ae..df8e3c269f70085deb881449e3410fd5c3a5b5ed 100644 (file)
@@ -40,6 +40,7 @@
 #include "ScriptDebugServer.h"
 #include "ScriptObject.h"
 #include "ScriptValue.h"
+#include <wtf/Stopwatch.h>
 #include <wtf/text/WTFString.h>
 
 namespace Inspector {
@@ -652,6 +653,8 @@ void InspectorDebuggerAgent::didPause(JSC::ExecState* scriptState, const Depreca
 
     if (m_listener)
         m_listener->didPause();
+    
+    m_injectedScriptManager->inspectorEnvironment().executionStopwatch()->stop();
 }
 
 void InspectorDebuggerAgent::breakpointActionSound(int breakpointActionIdentifier)
@@ -669,7 +672,7 @@ void InspectorDebuggerAgent::breakpointActionProbe(JSC::ExecState* scriptState,
         .setProbeId(action.identifier)
         .setSampleId(sampleId)
         .setBatchId(hitCount)
-        .setTimestamp(monotonicallyIncreasingTime())
+        .setTimestamp(m_injectedScriptManager->inspectorEnvironment().executionStopwatch()->elapsedTime())
         .setPayload(payload.release());
 
     m_frontendDispatcher->didSampleProbe(result.release());
@@ -679,6 +682,7 @@ void InspectorDebuggerAgent::didContinue()
 {
     m_pausedScriptState = nullptr;
     m_currentCallStack = Deprecated::ScriptValue();
+    m_injectedScriptManager->inspectorEnvironment().executionStopwatch()->start();
     clearBreakDetails();
 
     m_frontendDispatcher->resumed();
@@ -745,7 +749,6 @@ void InspectorDebuggerAgent::clearBreakDetails()
     m_breakAuxData = nullptr;
 }
 
-
 } // namespace Inspector
 
 #endif // ENABLE(INSPECTOR)
index a5d7e04cec73a3013085ae2051a8a6698e6f761f..cb4f85b3de5fb5dde8fc824c4e7b6fc49f69a1b8 100644 (file)
 #include <wtf/Vector.h>
 #include <wtf/text/StringHash.h>
 
+namespace WTF {
+class Stopwatch;
+}
+
 namespace Inspector {
 
 class InjectedScript;
@@ -163,6 +167,7 @@ private:
     RefPtr<InspectorObject> m_breakAuxData;
     bool m_enabled;
     bool m_javaScriptPauseScheduled;
+    RefPtr<WTF::Stopwatch> m_stopwatch;
     int m_nextProbeSampleId;
 };
 
index 4fe2ba6b1ce42b692a6c485703686fa9e4f7f7ff..e0ca2680bdb95475148fe4c41936dc6e96fc3a47 100644 (file)
@@ -32,7 +32,6 @@
 #include "CallFrame.h"
 #include "CodeBlock.h"
 #include "CommonIdentifiers.h"
-#include "DebuggerCallFrame.h"
 #include "InternalFunction.h"
 #include "JSFunction.h"
 #include "JSGlobalObject.h"
@@ -50,16 +49,16 @@ static unsigned ProfilesUID = 0;
 
 static CallIdentifier createCallIdentifierFromFunctionImp(ExecState*, JSObject*, const String& defaultSourceURL, unsigned defaultLineNumber, unsigned defaultColumnNumber);
 
-LegacyProfiler* LegacyProfiler::s_sharedLegacyProfiler = 0;
+LegacyProfiler* LegacyProfiler::s_sharedLegacyProfiler = nullptr;
 
 LegacyProfiler* LegacyProfiler::profiler()
 {
     if (!s_sharedLegacyProfiler)
         s_sharedLegacyProfiler = new LegacyProfiler();
     return s_sharedLegacyProfiler;
-}   
+}
 
-void LegacyProfiler::startProfiling(ExecState* exec, const String& title)
+void LegacyProfiler::startProfiling(ExecState* exec, const String& title, PassRefPtr<Stopwatch> stopwatch)
 {
     if (!exec)
         return;
@@ -75,14 +74,14 @@ void LegacyProfiler::startProfiling(ExecState* exec, const String& title)
     }
 
     exec->vm().setEnabledProfiler(this);
-    RefPtr<ProfileGenerator> profileGenerator = ProfileGenerator::create(exec, title, ++ProfilesUID);
+    RefPtr<ProfileGenerator> profileGenerator = ProfileGenerator::create(exec, title, ++ProfilesUID, stopwatch);
     m_currentProfiles.append(profileGenerator);
 }
 
 PassRefPtr<Profile> LegacyProfiler::stopProfiling(ExecState* exec, const String& title)
 {
     if (!exec)
-        return 0;
+        return nullptr;
 
     JSGlobalObject* origin = exec->lexicalGlobalObject();
     for (ptrdiff_t i = m_currentProfiles.size() - 1; i >= 0; --i) {
@@ -94,12 +93,12 @@ PassRefPtr<Profile> LegacyProfiler::stopProfiling(ExecState* exec, const String&
             m_currentProfiles.remove(i);
             if (!m_currentProfiles.size())
                 exec->vm().setEnabledProfiler(nullptr);
-            
+
             return returnProfile;
         }
     }
 
-    return 0;
+    return nullptr;
 }
 
 void LegacyProfiler::stopProfiling(JSGlobalObject* origin)
@@ -184,28 +183,6 @@ void LegacyProfiler::exceptionUnwind(ExecState* handlerCallFrame)
     callFunctionForProfilesWithGroup(std::bind(&ProfileGenerator::exceptionUnwind, std::placeholders::_1, handlerCallFrame, callIdentifier), m_currentProfiles, handlerCallFrame->lexicalGlobalObject()->profileGroup());
 }
 
-void LegacyProfiler::didPause(PassRefPtr<DebuggerCallFrame> prpCallFrame)
-{
-    if (m_currentProfiles.isEmpty())
-        return;
-
-    RefPtr<DebuggerCallFrame> callFrame = prpCallFrame;
-    CallIdentifier callIdentifier = createCallIdentifier(callFrame->exec(), JSValue(), StringImpl::empty(), 0, 0);
-
-    callFunctionForProfilesWithGroup(std::bind(&ProfileGenerator::didPause, std::placeholders::_1, callFrame, callIdentifier), m_currentProfiles, callFrame->vmEntryGlobalObject()->profileGroup());
-}
-
-void LegacyProfiler::didContinue(PassRefPtr<DebuggerCallFrame> prpCallFrame)
-{
-    if (m_currentProfiles.isEmpty())
-        return;
-
-    RefPtr<DebuggerCallFrame> callFrame = prpCallFrame;
-    CallIdentifier callIdentifier = createCallIdentifier(callFrame->exec(), JSValue(), StringImpl::empty(), 0, 0);
-
-    callFunctionForProfilesWithGroup(std::bind(&ProfileGenerator::didContinue, std::placeholders::_1, callFrame, callIdentifier), m_currentProfiles, callFrame->vmEntryGlobalObject()->profileGroup());
-}
-
 CallIdentifier LegacyProfiler::createCallIdentifier(ExecState* exec, JSValue functionValue, const String& defaultSourceURL, unsigned defaultLineNumber, unsigned defaultColumnNumber)
 {
     if (!functionValue)
index 1d6cb26f83ff02f8cad3270d14e0143660ab76af..8f6af40ee99ea4a90501e7eeea7b90275cee3ea3 100644 (file)
 #include "Profile.h"
 #include <wtf/PassRefPtr.h>
 #include <wtf/RefPtr.h>
+#include <wtf/Stopwatch.h>
 #include <wtf/Vector.h>
 
 namespace JSC {
 
-class DebuggerCallFrame;
 class ExecState;
-class VM;
 class JSGlobalObject;
 class JSObject;
 class JSValue;
@@ -48,10 +47,10 @@ struct CallIdentifier;
 class LegacyProfiler {
     WTF_MAKE_FAST_ALLOCATED;
 public:
-    JS_EXPORT_PRIVATE static LegacyProfiler* profiler(); 
+    JS_EXPORT_PRIVATE static LegacyProfiler* profiler();
     static CallIdentifier createCallIdentifier(ExecState*, JSValue, const WTF::String& sourceURL, unsigned defaultLineNumber, unsigned defaultColumnNumber);
 
-    JS_EXPORT_PRIVATE void startProfiling(ExecState*, const WTF::String& title);
+    JS_EXPORT_PRIVATE void startProfiling(ExecState*, const WTF::String& title, PassRefPtr<Stopwatch>);
     JS_EXPORT_PRIVATE PassRefPtr<Profile> stopProfiling(ExecState*, const WTF::String& title);
     void stopProfiling(JSGlobalObject*);
 
@@ -66,9 +65,6 @@ public:
 
     void exceptionUnwind(ExecState* handlerCallFrame);
 
-    void didPause(PassRefPtr<DebuggerCallFrame>);
-    void didContinue(PassRefPtr<DebuggerCallFrame>);
-
     const Vector<RefPtr<ProfileGenerator>>& currentProfiles() { return m_currentProfiles; };
 
 private:
index 752b6cc2de23380f5a2742ce4830c15805f837ba..6d62421fbd2f4417549a1eed0e17e049ab98d8ef 100644 (file)
@@ -44,7 +44,7 @@ Profile::Profile(const String& title, unsigned uid)
     // FIXME: When multi-threading is supported this will be a vector and calls
     // into the profiler will need to know which thread it is executing on.
     m_rootNode = ProfileNode::create(nullptr, CallIdentifier(ASCIILiteral("Thread_1"), String(), 0, 0), nullptr);
-    m_rootNode->appendCall(ProfileNode::Call(currentTime()));
+    m_rootNode->appendCall(ProfileNode::Call(0.0));
 }
 
 Profile::~Profile()
index 763b82ced807623914bfb676779bf5316995c60f..15421bbe818e76d14e68c5357f16fe511f02320f 100644 (file)
@@ -28,7 +28,6 @@
 
 #include "CallFrame.h"
 #include "CodeBlock.h"
-#include "Debugger.h"
 #include "JSGlobalObject.h"
 #include "JSStringRef.h"
 #include "JSFunction.h"
 
 namespace JSC {
 
-PassRefPtr<ProfileGenerator> ProfileGenerator::create(ExecState* exec, const String& title, unsigned uid)
+PassRefPtr<ProfileGenerator> ProfileGenerator::create(ExecState* exec, const String& title, unsigned uid, PassRefPtr<Stopwatch> stopwatch)
 {
-    return adoptRef(new ProfileGenerator(exec, title, uid));
+    return adoptRef(new ProfileGenerator(exec, title, uid, stopwatch));
 }
 
-ProfileGenerator::ProfileGenerator(ExecState* exec, const String& title, unsigned uid)
+ProfileGenerator::ProfileGenerator(ExecState* exec, const String& title, unsigned uid, PassRefPtr<Stopwatch> stopwatch)
     : m_origin(exec ? exec->lexicalGlobalObject() : nullptr)
     , m_profileGroup(exec ? exec->lexicalGlobalObject()->profileGroup() : 0)
-    , m_debuggerPausedTimestamp(NAN)
+    , m_stopwatch(stopwatch)
     , m_foundConsoleStartParent(false)
     , m_suspended(false)
 {
-    if (Debugger* debugger = exec->lexicalGlobalObject()->debugger())
-        m_debuggerPausedTimestamp = debugger->isPaused() ? currentTime() : NAN;
-
     m_profile = Profile::create(title, uid);
     m_currentNode = m_rootNode = m_profile->rootNode();
     if (exec)
@@ -85,7 +81,9 @@ public:
         unsigned column = 0;
         visitor->computeLineAndColumn(line, column);
         m_currentNode = ProfileNode::create(m_exec, LegacyProfiler::createCallIdentifier(m_exec, visitor->callee(), visitor->sourceURL(), line, column), m_rootNode.get());
-        m_currentNode->appendCall(ProfileNode::Call(currentTime()));
+        // Assume that profile times are relative to when the |console.profile| command is evaluated.
+        // This matches the logic in JSStartProfiling() and InspectorTimelineAgent::startFromConsole().
+        m_currentNode->appendCall(ProfileNode::Call(0.0));
         m_rootNode->spliceNode(m_currentNode.get());
 
         m_foundParent = true;
@@ -118,12 +116,7 @@ void ProfileGenerator::beginCallEntry(ProfileNode* node, double startTime)
     ASSERT_ARG(node, node);
 
     if (std::isnan(startTime))
-        startTime = currentTime();
-
-    // If the debugger is paused when beginning, then don't set the start time. It
-    // will be fixed up when the debugger unpauses or the call entry ends.
-    if (!std::isnan(m_debuggerPausedTimestamp))
-        startTime = NAN;
+        startTime = m_stopwatch->elapsedTime();
 
     node->appendCall(ProfileNode::Call(startTime));
 }
@@ -134,21 +127,8 @@ void ProfileGenerator::endCallEntry(ProfileNode* node)
 
     ProfileNode::Call& last = node->lastCall();
 
-    // If the debugger is paused, ignore the interval that ends now.
-    if (!std::isnan(m_debuggerPausedTimestamp) && !std::isnan(last.elapsedTime()))
-        return;
-
-    // If paused and no time was accrued then the debugger was never unpaused. The call will
-    // have no time accrued and appear to have started when the debugger was paused.
-    if (!std::isnan(m_debuggerPausedTimestamp)) {
-        last.setStartTime(m_debuggerPausedTimestamp);
-        last.setElapsedTime(0.0);
-        return;
-    }
-
-    // Otherwise, add the interval ending now to elapsed time.
     double previousElapsedTime = std::isnan(last.elapsedTime()) ? 0.0 : last.elapsedTime();
-    double newlyElapsedTime = currentTime() - last.startTime();
+    double newlyElapsedTime = m_stopwatch->elapsedTime() - last.startTime();
     last.setElapsedTime(previousElapsedTime + newlyElapsedTime);
 }
 
@@ -180,7 +160,7 @@ void ProfileGenerator::willExecute(ExecState* callerCallFrame, const CallIdentif
     }
 
     m_currentNode = calleeNode;
-    beginCallEntry(calleeNode.get());
+    beginCallEntry(calleeNode.get(), m_stopwatch->elapsedTime());
 }
 
 void ProfileGenerator::didExecute(ExecState* callerCallFrame, const CallIdentifier& callIdentifier)
@@ -223,33 +203,6 @@ void ProfileGenerator::exceptionUnwind(ExecState* handlerCallFrame, const CallId
     }
 }
 
-void ProfileGenerator::didPause(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&)
-{
-    ASSERT(std::isnan(m_debuggerPausedTimestamp));
-
-    m_debuggerPausedTimestamp = currentTime();
-
-    for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent()) {
-        ProfileNode::Call& last = node->lastCall();
-        ASSERT(!std::isnan(last.startTime()));
-
-        double previousElapsedTime = std::isnan(last.elapsedTime()) ? 0.0 : last.elapsedTime();
-        double additionalElapsedTime = m_debuggerPausedTimestamp - last.startTime();
-        last.setStartTime(NAN);
-        last.setElapsedTime(previousElapsedTime + additionalElapsedTime);
-    }
-}
-
-void ProfileGenerator::didContinue(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&)
-{
-    ASSERT(!std::isnan(m_debuggerPausedTimestamp));
-
-    for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent())
-        node->lastCall().setStartTime(m_debuggerPausedTimestamp);
-
-    m_debuggerPausedTimestamp = NAN;
-}
-
 void ProfileGenerator::stopProfiling()
 {
     for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent())
index 9f51a3dc86cd33f41a4e4beafe9f50c3ccc941f2..c5746d39df5006340320b99803e5adf61b150699 100644 (file)
@@ -29,6 +29,7 @@
 #include <wtf/PassRefPtr.h>
 #include <wtf/RefCounted.h>
 #include <wtf/RefPtr.h>
+#include <wtf/Stopwatch.h>
 #include <wtf/text/WTFString.h>
 
 namespace JSC {
@@ -42,7 +43,7 @@ namespace JSC {
 
     class ProfileGenerator : public RefCounted<ProfileGenerator>  {
     public:
-        static PassRefPtr<ProfileGenerator> create(ExecState*, const WTF::String& title, unsigned uid);
+        static PassRefPtr<ProfileGenerator> create(ExecState*, const WTF::String& title, unsigned uid, PassRefPtr<Stopwatch>);
 
         // Members
         const WTF::String& title() const;
@@ -54,28 +55,24 @@ namespace JSC {
         void didExecute(ExecState* callerCallFrame, const CallIdentifier&);
         void exceptionUnwind(ExecState* handlerCallFrame, const CallIdentifier&);
 
-        void didPause(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&);
-        void didContinue(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&);
-
         void setIsSuspended(bool suspended) { ASSERT(m_suspended != suspended); m_suspended = suspended; }
 
         void stopProfiling();
 
     private:
-        ProfileGenerator(ExecState*, const WTF::String& title, unsigned uid);
+        ProfileGenerator(ExecState*, const WTF::String& title, unsigned uid, PassRefPtr<Stopwatch>);
         void addParentForConsoleStart(ExecState*);
 
         void removeProfileStart();
         void removeProfileEnd();
 
-        void beginCallEntry(ProfileNode*, double startTime = NAN);
+        void beginCallEntry(ProfileNode*, double startTime);
         void endCallEntry(ProfileNode*);
 
         RefPtr<Profile> m_profile;
         JSGlobalObject* m_origin;
         unsigned m_profileGroup;
-        // Timestamp is set to NAN when the debugger is not currently paused.
-        double m_debuggerPausedTimestamp;
+        RefPtr<Stopwatch> m_stopwatch;
         RefPtr<ProfileNode> m_rootNode;
         RefPtr<ProfileNode> m_currentNode;
         bool m_foundConsoleStartParent;
index 5faa425c7104d1f7496230a534e1d0c57a03e744..6eeaa87d05232f9919ba3736c70ce6fd283e7a94 100644 (file)
@@ -1,3 +1,22 @@
+2014-10-18  Brian J. Burg  <burg@cs.washington.edu>
+
+        Web Inspector: timelines should not count time elapsed while paused in the debugger
+        https://bugs.webkit.org/show_bug.cgi?id=136351
+
+        Reviewed by Timothy Hatcher.
+
+        * WTF.vcxproj/WTF.vcxproj:
+        * WTF.vcxproj/WTF.vcxproj.filters:
+        * WTF.xcodeproj/project.pbxproj:
+        * wtf/CMakeLists.txt:
+        * wtf/Stopwatch.h: Added. This implements a refcounted monotonic stopwatch.
+        (WTF::Stopwatch::create):
+        (WTF::Stopwatch::Stopwatch):
+        (WTF::Stopwatch::reset):
+        (WTF::Stopwatch::start):
+        (WTF::Stopwatch::stop):
+        (WTF::Stopwatch::elapsedTime):
+
 2014-10-23  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Provide a way to have alternate inspector agents
index 0f83bae9b587ee9d38d1b9060dc4ec6c4e6f1042..2f3ac209c526c034ff40ae5e3644426f6bab6b96 100644 (file)
     <ClInclude Include="..\wtf\StackBounds.h" />
     <ClInclude Include="..\wtf\StaticConstructors.h" />
     <ClInclude Include="..\wtf\StdLibExtras.h" />
+    <ClInclude Include="..\wtf\Stopwatch.h" />
     <ClInclude Include="..\wtf\StringExtras.h" />
     <ClInclude Include="..\wtf\StringHasher.h" />
     <ClInclude Include="..\wtf\StringPrintStream.h" />
index 6a0e9a162d957552c8f77e211356ebf3a4c54ca3..5953a0f42afd75076bf985d59830d9bec8635f75 100644 (file)
     <ClInclude Include="..\wtf\StdLibExtras.h">
       <Filter>wtf</Filter>
     </ClInclude>
+    <ClInclude Include="..\wtf\Stopwatch.h">
+      <Filter>wtf</Filter>
+    </ClInclude>
     <ClInclude Include="..\wtf\StringExtras.h">
       <Filter>wtf</Filter>
     </ClInclude>
index 512e0e21e9b4d5278054a89efc6c110184795acf..ff48da0cc531282a12d0d5106c66035f155d9156 100644 (file)
                A8A47487151A825B004123FF /* WTFThreadData.h in Headers */ = {isa = PBXBuildFile; fileRef = A8A4737B151A825B004123FF /* WTFThreadData.h */; };
                A8A4748C151A8264004123FF /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = A8A4748B151A8264004123FF /* config.h */; };
                B38FD7BD168953E80065C969 /* FeatureDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = B38FD7BC168953E80065C969 /* FeatureDefines.h */; };
+               C4F8A93719C65EB400B2B15D /* Stopwatch.h in Headers */ = {isa = PBXBuildFile; fileRef = C4F8A93619C65EB400B2B15D /* Stopwatch.h */; };
                CD5497AC15857D0300B5BC30 /* MediaTime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD5497AA15857D0300B5BC30 /* MediaTime.cpp */; };
                CD5497AD15857D0300B5BC30 /* MediaTime.h in Headers */ = {isa = PBXBuildFile; fileRef = CD5497AB15857D0300B5BC30 /* MediaTime.h */; };
                CE46516E19DB1FB4003ECA05 /* NSMapTableSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = CE46516D19DB1FB4003ECA05 /* NSMapTableSPI.h */; };
                A8A4737B151A825B004123FF /* WTFThreadData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WTFThreadData.h; sourceTree = "<group>"; };
                A8A4748B151A8264004123FF /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = "<group>"; };
                B38FD7BC168953E80065C969 /* FeatureDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeatureDefines.h; sourceTree = "<group>"; };
+               C4F8A93619C65EB400B2B15D /* Stopwatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Stopwatch.h; sourceTree = "<group>"; };
                CD5497AA15857D0300B5BC30 /* MediaTime.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MediaTime.cpp; sourceTree = "<group>"; };
                CD5497AB15857D0300B5BC30 /* MediaTime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaTime.h; sourceTree = "<group>"; };
                CE46516D19DB1FB4003ECA05 /* NSMapTableSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSMapTableSPI.h; sourceTree = "<group>"; };
                                FEDACD3C1630F83F00C69634 /* StackStats.h */,
                                A8A47310151A825B004123FF /* StaticConstructors.h */,
                                A8A47311151A825B004123FF /* StdLibExtras.h */,
+                               C4F8A93619C65EB400B2B15D /* Stopwatch.h */,
                                1A6BB768162F300500DD16DB /* StreamBuffer.h */,
                                A8A47313151A825B004123FF /* StringExtras.h */,
                                A748745117A0BDAE00FA04CB /* StringHashDumpContext.h */,
                                A8A473A3151A825B004123FF /* DecimalNumber.h in Headers */,
                                0F2B66A617B6B4FB00A7AE3F /* DeferrableRefCounted.h in Headers */,
                                A8A473A5151A825B004123FF /* Deque.h in Headers */,
+                               C4F8A93719C65EB400B2B15D /* Stopwatch.h in Headers */,
                                A8A473A6151A825B004123FF /* DisallowCType.h in Headers */,
                                A8A473AF151A825B004123FF /* diy-fp.h in Headers */,
                                A8A473B1151A825B004123FF /* double-conversion.h in Headers */,
index 2aad793c8170b11df2e108eb13471607ab0a5e2d..bc26ac41d25c7b371000f4edbd0e27772a045577 100644 (file)
@@ -92,6 +92,7 @@ set(WTF_HEADERS
     StackStats.h
     StaticConstructors.h
     StdLibExtras.h
+    Stopwatch.h
     StringExtras.h
     StringHasher.h
     StringPrintStream.h
diff --git a/Source/WTF/wtf/Stopwatch.h b/Source/WTF/wtf/Stopwatch.h
new file mode 100644 (file)
index 0000000..d7e5db1
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 University of Washington. 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+#ifndef Stopwatch_h
+#define Stopwatch_h
+
+#include <cmath>
+#include <wtf/CurrentTime.h>
+#include <wtf/RefCounted.h>
+
+namespace WTF {
+
+class Stopwatch : public RefCounted<Stopwatch> {
+public:
+    static PassRefPtr<Stopwatch> create()
+    {
+        return adoptRef(new Stopwatch());
+    }
+
+    void reset();
+    void start();
+    void stop();
+
+    double elapsedTime();
+
+private:
+    Stopwatch()
+        : m_elapsedTime(0.0)
+        , m_lastStartTime(NAN)
+    {
+    }
+
+    double m_elapsedTime;
+    double m_lastStartTime;
+};
+
+inline void Stopwatch::reset()
+{
+    m_elapsedTime = 0.0;
+    m_lastStartTime = NAN;
+}
+
+inline void Stopwatch::start()
+{
+    ASSERT(isnan(m_lastStartTime));
+
+    m_lastStartTime = monotonicallyIncreasingTime();
+}
+
+inline void Stopwatch::stop()
+{
+    ASSERT(!isnan(m_lastStartTime));
+
+    m_elapsedTime += monotonicallyIncreasingTime() - m_lastStartTime;
+    m_lastStartTime = NAN;
+}
+
+inline double Stopwatch::elapsedTime()
+{
+    bool shouldSuspend = !isnan(m_lastStartTime);
+    if (shouldSuspend)
+        stop();
+
+    double elapsedTime = m_elapsedTime;
+
+    if (shouldSuspend)
+        start();
+    return elapsedTime;
+}
+
+} // namespace WTF
+
+using WTF::Stopwatch;
+
+#endif // Stopwatch_h
index e30ced1b28ee0c93da0d7a500ae0d50784109461..0256055124f0fd950ee29567be0cdf0cbf56a057 100644 (file)
@@ -1,3 +1,67 @@
+2014-10-18  Brian J. Burg  <burg@cs.washington.edu>
+
+        Web Inspector: timelines should not count time elapsed while paused in the debugger
+        https://bugs.webkit.org/show_bug.cgi?id=136351
+
+        Reviewed by Timothy Hatcher.
+
+        To avoid counting time elapsed while the debugger is paused, timeline records should
+        keep track of time elapsed since the start of timeline capturing, rather than wall clock
+        timestamps. We can easily compute elapsed time by sharing a Stopwatch instance through the
+        inspector environment. The stopwatch runs with timelines and is paused with the debugger,
+        so subsequent time measurements will not include time elapsed while the debugger is paused.
+
+        This refactoring is safe because start and end times are only used to graph records; the
+        timestamp's actual value is irrelevant and is not displayed in the user interface. Date
+        timestamps are still included with network-related records as part of their header data.
+
+        No new tests, because we cannot reliably test timing changes induced by debugger pauses.
+        It is possible for records to accrue time before the debugger pauses or after it resumes.
+
+        * inspector/InspectorCSSAgent.cpp: Remove unnecessary include.
+        * inspector/InspectorController.cpp:
+        (WebCore::InspectorController::InspectorController):
+        (WebCore::InspectorController::executionStopwatch): Add a shared stopwatch.
+        * inspector/InspectorController.h:
+        * inspector/InspectorPageAgent.cpp:
+        (WebCore::InspectorPageAgent::timestamp): Redirect to the shared stopwatch.
+        (WebCore::InspectorPageAgent::domContentEventFired):
+        (WebCore::InspectorPageAgent::loadEventFired):
+        * inspector/InspectorPageAgent.h:
+        * inspector/InspectorResourceAgent.cpp:
+        (WebCore::InspectorResourceAgent::timestamp): Redirect to the shared stopwatch.
+        (WebCore::InspectorResourceAgent::willSendRequest):
+        (WebCore::InspectorResourceAgent::didReceiveResponse):
+        (WebCore::InspectorResourceAgent::didReceiveData):
+        (WebCore::InspectorResourceAgent::didFinishLoading):
+        (WebCore::InspectorResourceAgent::didFailLoading):
+        (WebCore::InspectorResourceAgent::didLoadResourceFromMemoryCache):
+        (WebCore::InspectorResourceAgent::willSendWebSocketHandshakeRequest):
+        (WebCore::InspectorResourceAgent::didReceiveWebSocketHandshakeResponse):
+        (WebCore::InspectorResourceAgent::didCloseWebSocket):
+        (WebCore::InspectorResourceAgent::didReceiveWebSocketFrame):
+        (WebCore::InspectorResourceAgent::didSendWebSocketFrame):
+        (WebCore::InspectorResourceAgent::didReceiveWebSocketFrameError):
+        * inspector/InspectorResourceAgent.h:
+        * inspector/InspectorTimelineAgent.cpp:
+        (WebCore::InspectorTimelineAgent::internalStart): Start and stop the stopwatch with timelines.
+        (WebCore::InspectorTimelineAgent::internalStop):
+        (WebCore::InspectorTimelineAgent::timestamp): Redirect to the shared stopwatch.
+        (WebCore::startProfiling):
+        (WebCore::InspectorTimelineAgent::startFromConsole):
+        (WebCore::InspectorTimelineAgent::willCallFunction):
+        (WebCore::InspectorTimelineAgent::willEvaluateScript):
+        (WebCore::TimelineTimeConverter::reset): Deleted.
+        * inspector/InspectorTimelineAgent.h:
+        (WebCore::TimelineTimeConverter::TimelineTimeConverter): Deleted.
+        (WebCore::TimelineTimeConverter::fromMonotonicallyIncreasingTime): Deleted.
+        (WebCore::InspectorTimelineAgent::timeConverter): Deleted.
+        * inspector/TimelineRecordFactory.cpp:
+        * inspector/WorkerInspectorController.cpp:
+        (WebCore::WorkerInspectorController::WorkerInspectorController):
+        (WebCore::WorkerInspectorController::executionStopwatch): Add a shared stopwatch.
+        * inspector/WorkerInspectorController.h:
+
 2014-10-25  Dan Bernstein  <mitz@apple.com>
 
         Fix builds using the public SDK.
index 36e1982ca800d6136918e74528839184cb7274b1..71c8c58039628d2bbe4e952672ab7d1c4841e828 100644 (file)
@@ -56,7 +56,6 @@
 #include "StyleSheetList.h"
 #include "WebKitNamedFlow.h"
 #include <inspector/InspectorProtocolObjects.h>
-#include <wtf/CurrentTime.h>
 #include <wtf/HashSet.h>
 #include <wtf/Ref.h>
 #include <wtf/Vector.h>
index 7f5af07fdfefa5ac0b968e25da2f643b489706e4..86cec047d331dbb3d9018c628341d4762c506aa6 100644 (file)
@@ -73,6 +73,7 @@
 #include <inspector/agents/InspectorAgent.h>
 #include <profiler/LegacyProfiler.h>
 #include <runtime/JSLock.h>
+#include <wtf/Stopwatch.h>
 
 #if ENABLE(REMOTE_INSPECTOR)
 #include "PageDebuggable.h"
@@ -88,6 +89,7 @@ InspectorController::InspectorController(Page& page, InspectorClient* inspectorC
     , m_injectedScriptManager(std::make_unique<WebInjectedScriptManager>(*this, WebInjectedScriptHost::create()))
     , m_overlay(std::make_unique<InspectorOverlay>(page, inspectorClient))
     , m_inspectorFrontendChannel(nullptr)
+    , m_executionStopwatch(Stopwatch::create())
     , m_page(page)
     , m_inspectorClient(inspectorClient)
     , m_inspectorFrontendClient(nullptr)
@@ -452,6 +454,11 @@ void InspectorController::frontendInitialized()
 #endif
 }
 
+PassRefPtr<Stopwatch> InspectorController::executionStopwatch()
+{
+    return m_executionStopwatch;
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(INSPECTOR)
index 9475bc2507862212e1594210ae6cc498bfe91855..96f41f52db4b9ea14c0df7b779be1ccb21b015e0 100644 (file)
@@ -130,6 +130,7 @@ public:
     virtual void willCallInjectedScriptFunction(JSC::ExecState*, const String& scriptName, int scriptLine) override;
     virtual void didCallInjectedScriptFunction(JSC::ExecState*) override;
     virtual void frontendInitialized() override;
+    virtual PassRefPtr<WTF::Stopwatch> executionStopwatch() override;
 
 private:
     friend InstrumentingAgents* instrumentationForPage(Page*);
@@ -148,6 +149,7 @@ private:
 
     RefPtr<Inspector::InspectorBackendDispatcher> m_inspectorBackendDispatcher;
     Inspector::InspectorFrontendChannel* m_inspectorFrontendChannel;
+    RefPtr<WTF::Stopwatch> m_executionStopwatch;
     Page& m_page;
     InspectorClient* m_inspectorClient;
     InspectorFrontendClient* m_inspectorFrontendClient;
index 5fd22728860fc032f94acdb5ada4a0f06d9437fa..782826dc7d9908cf4d9757c6a86e76b3bd0089d3 100644 (file)
@@ -57,6 +57,7 @@
 #include "InspectorDOMAgent.h"
 #include "InspectorInstrumentation.h"
 #include "InspectorOverlay.h"
+#include "InspectorTimelineAgent.h"
 #include "InstrumentingAgents.h"
 #include "MainFrame.h"
 #include "MemoryCache.h"
@@ -72,7 +73,6 @@
 #include <inspector/ContentSearchUtilities.h>
 #include <inspector/IdentifiersFactory.h>
 #include <inspector/InspectorValues.h>
-#include <wtf/CurrentTime.h>
 #include <wtf/ListHashSet.h>
 #include <wtf/text/Base64.h>
 #include <wtf/text/StringBuilder.h>
@@ -359,6 +359,11 @@ void InspectorPageAgent::willDestroyFrontendAndBackend(InspectorDisconnectReason
 #endif
 }
 
+double InspectorPageAgent::timestamp()
+{
+    return m_instrumentingAgents->inspectorEnvironment().executionStopwatch()->elapsedTime();
+}
+
 void InspectorPageAgent::enable(ErrorString&)
 {
     m_enabled = true;
@@ -709,12 +714,12 @@ void InspectorPageAgent::didClearWindowObjectInWorld(Frame* frame, DOMWrapperWor
 void InspectorPageAgent::domContentEventFired()
 {
     m_isFirstLayoutAfterOnLoad = true;
-    m_frontendDispatcher->domContentEventFired(currentTime());
+    m_frontendDispatcher->domContentEventFired(timestamp());
 }
 
 void InspectorPageAgent::loadEventFired()
 {
-    m_frontendDispatcher->loadEventFired(currentTime());
+    m_frontendDispatcher->loadEventFired(timestamp());
 }
 
 void InspectorPageAgent::frameNavigated(DocumentLoader* loader)
index ace108abd19e37b8f79a297461d49fe6fba6a7e7..389af6f824c8277fcbc3663a14fb37ad15583432 100644 (file)
@@ -161,6 +161,8 @@ private:
     void updateTouchEventEmulationInPage(bool);
 #endif
 
+    double timestamp();
+
     static bool mainResourceContent(Frame*, bool withBase64Encode, String* result);
     static bool dataContent(const char* data, unsigned size, const String& textEncodingName, bool withBase64Encode, String* result);
 
index dc3d9b89909fdbdb1aca4d17fa7a0354a3d18956..1c57a4f57ef95991161efafa08d047f0778952d5 100644 (file)
@@ -47,6 +47,7 @@
 #include "IconController.h"
 #include "InspectorClient.h"
 #include "InspectorPageAgent.h"
+#include "InspectorTimelineAgent.h"
 #include "InstrumentingAgents.h"
 #include "JSMainThreadExecState.h"
 #include "MemoryCache.h"
@@ -68,7 +69,6 @@
 #include <inspector/InspectorValues.h>
 #include <inspector/ScriptCallStack.h>
 #include <inspector/ScriptCallStackFactory.h>
-#include <wtf/CurrentTime.h>
 #include <wtf/RefPtr.h>
 #include <wtf/text/StringBuilder.h>
 
@@ -275,6 +275,11 @@ InspectorResourceAgent::~InspectorResourceAgent()
     ASSERT(!m_instrumentingAgents->inspectorResourceAgent());
 }
 
+double InspectorResourceAgent::timestamp()
+{
+    return m_instrumentingAgents->inspectorEnvironment().executionStopwatch()->elapsedTime();
+}
+
 void InspectorResourceAgent::willSendRequest(unsigned long identifier, DocumentLoader* loader, ResourceRequest& request, const ResourceResponse& redirectResponse)
 {
     if (request.hiddenFromInspector()) {
@@ -319,7 +324,7 @@ void InspectorResourceAgent::willSendRequest(unsigned long identifier, DocumentL
     Inspector::Protocol::Page::ResourceType resourceType = InspectorPageAgent::resourceTypeJson(type);
 
     RefPtr<Inspector::Protocol::Network::Initiator> initiatorObject = buildInitiatorObject(loader->frame() ? loader->frame()->document() : nullptr);
-    m_frontendDispatcher->requestWillBeSent(requestId, m_pageAgent->frameId(loader->frame()), m_pageAgent->loaderId(loader), loader->url().string(), buildObjectForResourceRequest(request), currentTime(), initiatorObject, buildObjectForResourceResponse(redirectResponse, loader), type != InspectorPageAgent::OtherResource ? &resourceType : nullptr);
+    m_frontendDispatcher->requestWillBeSent(requestId, m_pageAgent->frameId(loader->frame()), m_pageAgent->loaderId(loader), loader->url().string(), buildObjectForResourceRequest(request), timestamp(), initiatorObject, buildObjectForResourceResponse(redirectResponse, loader), type != InspectorPageAgent::OtherResource ? &resourceType : nullptr);
 }
 
 void InspectorResourceAgent::markResourceAsCached(unsigned long identifier)
@@ -364,7 +369,7 @@ void InspectorResourceAgent::didReceiveResponse(unsigned long identifier, Docume
     m_resourcesData->responseReceived(requestId, m_pageAgent->frameId(loader->frame()), response);
     m_resourcesData->setResourceType(requestId, type);
 
-    m_frontendDispatcher->responseReceived(requestId, m_pageAgent->frameId(loader->frame()), m_pageAgent->loaderId(loader), currentTime(), InspectorPageAgent::resourceTypeJson(type), resourceResponse);
+    m_frontendDispatcher->responseReceived(requestId, m_pageAgent->frameId(loader->frame()), m_pageAgent->loaderId(loader), timestamp(), InspectorPageAgent::resourceTypeJson(type), resourceResponse);
 
     // If we revalidated the resource and got Not modified, send content length following didReceiveResponse
     // as there will be no calls to didReceiveData from the network stack.
@@ -390,7 +395,7 @@ void InspectorResourceAgent::didReceiveData(unsigned long identifier, const char
             m_resourcesData->maybeAddResourceData(requestId, data, dataLength);
     }
 
-    m_frontendDispatcher->dataReceived(requestId, currentTime(), dataLength, encodedDataLength);
+    m_frontendDispatcher->dataReceived(requestId, timestamp(), dataLength, encodedDataLength);
 }
 
 void InspectorResourceAgent::didFinishLoading(unsigned long identifier, DocumentLoader* loader, double finishTime)
@@ -410,7 +415,7 @@ void InspectorResourceAgent::didFinishLoading(unsigned long identifier, Document
     // However, all other times passed to the Inspector are generated from the web process. Mixing
     // times from different processes can cause the finish time to be earlier than the response
     // received time due to inter-process communication lag.
-    finishTime = currentTime();
+    finishTime = timestamp();
 
     String sourceMappingURL;
     NetworkResourcesData::ResourceData const* resourceData = m_resourcesData->data(requestId);
@@ -436,7 +441,7 @@ void InspectorResourceAgent::didFailLoading(unsigned long identifier, DocumentLo
     }
 
     bool canceled = error.isCancellation();
-    m_frontendDispatcher->loadingFailed(requestId, currentTime(), error.localizedDescription(), canceled ? &canceled : nullptr);
+    m_frontendDispatcher->loadingFailed(requestId, timestamp(), error.localizedDescription(), canceled ? &canceled : nullptr);
 }
 
 void InspectorResourceAgent::didLoadResourceFromMemoryCache(DocumentLoader* loader, CachedResource* resource)
@@ -454,7 +459,7 @@ void InspectorResourceAgent::didLoadResourceFromMemoryCache(DocumentLoader* load
 
     RefPtr<Inspector::Protocol::Network::Initiator> initiatorObject = buildInitiatorObject(loader->frame() ? loader->frame()->document() : nullptr);
 
-    m_frontendDispatcher->requestServedFromMemoryCache(requestId, frameId, loaderId, loader->url().string(), currentTime(), initiatorObject, buildObjectForCachedResource(resource, loader));
+    m_frontendDispatcher->requestServedFromMemoryCache(requestId, frameId, loaderId, loader->url().string(), timestamp(), initiatorObject, buildObjectForCachedResource(resource, loader));
 }
 
 void InspectorResourceAgent::setInitialScriptContent(unsigned long identifier, const String& sourceString)
@@ -586,7 +591,7 @@ void InspectorResourceAgent::willSendWebSocketHandshakeRequest(unsigned long ide
 {
     RefPtr<Inspector::Protocol::Network::WebSocketRequest> requestObject = Inspector::Protocol::Network::WebSocketRequest::create()
         .setHeaders(buildObjectForHeaders(request.httpHeaderFields()));
-    m_frontendDispatcher->webSocketWillSendHandshakeRequest(IdentifiersFactory::requestId(identifier), currentTime(), requestObject);
+    m_frontendDispatcher->webSocketWillSendHandshakeRequest(IdentifiersFactory::requestId(identifier), timestamp(), requestObject);
 }
 
 void InspectorResourceAgent::didReceiveWebSocketHandshakeResponse(unsigned long identifier, const ResourceResponse& response)
@@ -595,12 +600,12 @@ void InspectorResourceAgent::didReceiveWebSocketHandshakeResponse(unsigned long
         .setStatus(response.httpStatusCode())
         .setStatusText(response.httpStatusText())
         .setHeaders(buildObjectForHeaders(response.httpHeaderFields()));
-    m_frontendDispatcher->webSocketHandshakeResponseReceived(IdentifiersFactory::requestId(identifier), currentTime(), responseObject);
+    m_frontendDispatcher->webSocketHandshakeResponseReceived(IdentifiersFactory::requestId(identifier), timestamp(), responseObject);
 }
 
 void InspectorResourceAgent::didCloseWebSocket(unsigned long identifier)
 {
-    m_frontendDispatcher->webSocketClosed(IdentifiersFactory::requestId(identifier), currentTime());
+    m_frontendDispatcher->webSocketClosed(IdentifiersFactory::requestId(identifier), timestamp());
 }
 
 void InspectorResourceAgent::didReceiveWebSocketFrame(unsigned long identifier, const WebSocketFrame& frame)
@@ -609,7 +614,7 @@ void InspectorResourceAgent::didReceiveWebSocketFrame(unsigned long identifier,
         .setOpcode(frame.opCode)
         .setMask(frame.masked)
         .setPayloadData(String(frame.payload, frame.payloadLength));
-    m_frontendDispatcher->webSocketFrameReceived(IdentifiersFactory::requestId(identifier), currentTime(), frameObject);
+    m_frontendDispatcher->webSocketFrameReceived(IdentifiersFactory::requestId(identifier), timestamp(), frameObject);
 }
 
 void InspectorResourceAgent::didSendWebSocketFrame(unsigned long identifier, const WebSocketFrame& frame)
@@ -618,12 +623,12 @@ void InspectorResourceAgent::didSendWebSocketFrame(unsigned long identifier, con
         .setOpcode(frame.opCode)
         .setMask(frame.masked)
         .setPayloadData(String(frame.payload, frame.payloadLength));
-    m_frontendDispatcher->webSocketFrameSent(IdentifiersFactory::requestId(identifier), currentTime(), frameObject);
+    m_frontendDispatcher->webSocketFrameSent(IdentifiersFactory::requestId(identifier), timestamp(), frameObject);
 }
 
 void InspectorResourceAgent::didReceiveWebSocketFrameError(unsigned long identifier, const String& errorMessage)
 {
-    m_frontendDispatcher->webSocketFrameError(IdentifiersFactory::requestId(identifier), currentTime(), errorMessage);
+    m_frontendDispatcher->webSocketFrameError(IdentifiersFactory::requestId(identifier), timestamp(), errorMessage);
 }
 
 #endif // ENABLE(WEB_SOCKETS)
index e42e1c176457b05adcee329c03762f330da47c25..2e203a65d3cea9161a8eceaa5f8016ad23c9367c 100644 (file)
@@ -138,6 +138,8 @@ public:
 private:
     void enable();
 
+    double timestamp();
+
     InspectorPageAgent* m_pageAgent;
     InspectorClient* m_client;
     std::unique_ptr<Inspector::InspectorNetworkFrontendDispatcher> m_frontendDispatcher;
index 5539fbadbb7467616ffe149d9b0e9e9356e788e9..7c63f95bcc1d3a94cf29477b8bea3676768ac9ab 100644 (file)
@@ -60,11 +60,6 @@ using namespace Inspector;
 
 namespace WebCore {
 
-void TimelineTimeConverter::reset()
-{
-    m_startOffset = monotonicallyIncreasingTime() - currentTime();
-}
-
 InspectorTimelineAgent::~InspectorTimelineAgent()
 {
 }
@@ -117,7 +112,7 @@ void InspectorTimelineAgent::internalStart(const int* maxCallStackDepth)
     else
         m_maxCallStackDepth = 5;
 
-    m_timeConverter.reset();
+    m_instrumentingAgents->inspectorEnvironment().executionStopwatch()->start();
 
     m_instrumentingAgents->setInspectorTimelineAgent(this);
 
@@ -135,6 +130,8 @@ void InspectorTimelineAgent::internalStop()
     if (!m_enabled)
         return;
 
+    m_instrumentingAgents->inspectorEnvironment().executionStopwatch()->stop();
+
     m_instrumentingAgents->setInspectorTimelineAgent(nullptr);
 
     if (m_scriptDebugServer)
@@ -148,6 +145,11 @@ void InspectorTimelineAgent::internalStop()
         m_frontendDispatcher->recordingStopped();
 }
 
+double InspectorTimelineAgent::timestamp()
+{
+    return m_instrumentingAgents->inspectorEnvironment().executionStopwatch()->elapsedTime();
+}
+
 void InspectorTimelineAgent::setPageScriptDebugServer(PageScriptDebugServer* scriptDebugServer)
 {
     ASSERT(!m_enabled);
@@ -156,9 +158,9 @@ void InspectorTimelineAgent::setPageScriptDebugServer(PageScriptDebugServer* scr
     m_scriptDebugServer = scriptDebugServer;
 }
 
-static inline void startProfiling(JSC::ExecState* exec, const String& title)
+static inline void startProfiling(JSC::ExecState* exec, const String& title, PassRefPtr<Stopwatch> stopwatch)
 {
-    JSC::LegacyProfiler::profiler()->startProfiling(exec, title);
+    JSC::LegacyProfiler::profiler()->startProfiling(exec, title, stopwatch);
 }
 
 static inline PassRefPtr<JSC::Profile> stopProfiling(JSC::ExecState* exec, const String& title)
@@ -166,9 +168,9 @@ static inline PassRefPtr<JSC::Profile> stopProfiling(JSC::ExecState* exec, const
     return JSC::LegacyProfiler::profiler()->stopProfiling(exec, title);
 }
 
-static inline void startProfiling(Frame* frame, const String& title)
+static inline void startProfiling(Frame* frame, const String& title, PassRefPtr<Stopwatch> stopwatch)
 {
-    startProfiling(toJSDOMWindow(frame, debuggerWorld())->globalExec(), title);
+    startProfiling(toJSDOMWindow(frame, debuggerWorld())->globalExec(), title, stopwatch);
 }
 
 static inline PassRefPtr<JSC::Profile> stopProfiling(Frame* frame, const String& title)
@@ -192,7 +194,11 @@ void InspectorTimelineAgent::startFromConsole(JSC::ExecState* exec, const String
     if (!m_enabled && m_pendingConsoleProfileRecords.isEmpty())
         internalStart();
 
-    startProfiling(exec, title);
+    // Use an independent stopwatch for console-initiated profiling, since the user will expect it
+    // to be relative to when their command was issued.
+    RefPtr<Stopwatch> profilerStopwatch = Stopwatch::create();
+    profilerStopwatch->start();
+    startProfiling(exec, title, profilerStopwatch.release());
 
     m_pendingConsoleProfileRecords.append(createRecordEntry(TimelineRecordFactory::createConsoleProfileData(title), TimelineRecordType::ConsoleProfile, true, frameFromExecState(exec)));
 }
@@ -231,7 +237,7 @@ void InspectorTimelineAgent::willCallFunction(const String& scriptName, int scri
     pushCurrentRecord(TimelineRecordFactory::createFunctionCallData(scriptName, scriptLine), TimelineRecordType::FunctionCall, true, frame);
 
     if (frame && !m_callStackDepth)
-        startProfiling(frame, ASCIILiteral("Timeline FunctionCall"));
+        startProfiling(frame, ASCIILiteral("Timeline FunctionCall"), m_instrumentingAgents->inspectorEnvironment().executionStopwatch());
 
     ++m_callStackDepth;
 }
@@ -406,7 +412,7 @@ void InspectorTimelineAgent::willEvaluateScript(const String& url, int lineNumbe
 
     if (frame && !m_callStackDepth) {
         ++m_callStackDepth;
-        startProfiling(frame, ASCIILiteral("Timeline EvaluateScript"));
+        startProfiling(frame, ASCIILiteral("Timeline EvaluateScript"), m_instrumentingAgents->inspectorEnvironment().executionStopwatch());
     }
 }
 
@@ -745,11 +751,6 @@ 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;
index 31c2f9b8dbdab571f890a7067ab901c73c28d3a4..b6cbb1ef07d38cca772c91b724bacc8cd0b89453 100644 (file)
@@ -40,6 +40,7 @@
 #include <inspector/InspectorFrontendDispatchers.h>
 #include <inspector/InspectorValues.h>
 #include <inspector/ScriptDebugListener.h>
+#include <wtf/Stopwatch.h>
 #include <wtf/Vector.h>
 #include <wtf/WeakPtr.h>
 
@@ -113,19 +114,6 @@ enum class TimelineRecordType {
     WebSocketDestroy
 };
 
-class TimelineTimeConverter {
-public:
-    TimelineTimeConverter()
-        : m_startOffset(0)
-    {
-    }
-    double fromMonotonicallyIncreasingTime(double time) const  { return (time - m_startOffset) * 1000.0; }
-    void reset();
-
-private:
-    double m_startOffset;
-};
-
 class InspectorTimelineAgent
     : public InspectorAgentBase
     , public Inspector::InspectorTimelineBackendDispatcherHandler
@@ -218,7 +206,7 @@ public:
 #endif
 
 protected:
-    // ScriptDebugListener. This is only used to create records for probe samples.
+    // ScriptDebugListener
     virtual void didParseSource(JSC::SourceID, const Script&) override { }
     virtual void failedToParseSource(const String&, const String&, int, int, const String&) override { }
     virtual void didPause(JSC::ExecState*, const Deprecated::ScriptValue&, const Deprecated::ScriptValue&) override { }
@@ -247,6 +235,7 @@ private:
 
     void internalStart(const int* maxCallStackDepth = nullptr);
     void internalStop();
+    double timestamp();
 
     void sendEvent(PassRefPtr<Inspector::InspectorObject>);
     void appendRecord(PassRefPtr<Inspector::InspectorObject> data, TimelineRecordType, bool captureCallStack, Frame*);
@@ -264,17 +253,13 @@ private:
     void clearRecordStack();
 
     void localToPageQuad(const RenderObject&, const LayoutRect&, FloatQuad*);
-    const TimelineTimeConverter& timeConverter() const { return m_timeConverter; }
-    double timestamp();
     Page* page();
 
     InspectorPageAgent* m_pageAgent;
     PageScriptDebugServer* m_scriptDebugServer;
-    TimelineTimeConverter m_timeConverter;
 
     std::unique_ptr<Inspector::InspectorTimelineFrontendDispatcher> m_frontendDispatcher;
     RefPtr<Inspector::InspectorTimelineBackendDispatcher> m_backendDispatcher;
-    double m_timestampOffset;
 
     Vector<TimelineRecordEntry> m_recordStack;
 
index d8eb65ca3458f59497c459b0a3c51ef5f28277da..bb5b98370e667d45bb345db5e7cc256d42b0fd29 100644 (file)
@@ -47,7 +47,6 @@
 #include <inspector/ScriptCallStack.h>
 #include <inspector/ScriptCallStackFactory.h>
 #include <profiler/Profile.h>
-#include <wtf/CurrentTime.h>
 
 using namespace Inspector;
 
index 7ed3ed924c92b949fea862bdd755198344141290..aba98d28ea598ad7a93954d4bfa2520316e549c6 100644 (file)
@@ -51,6 +51,7 @@
 #include "WorkerThread.h"
 #include <inspector/InspectorBackendDispatcher.h>
 #include <inspector/InspectorFrontendDispatchers.h>
+#include <wtf/Stopwatch.h>
 
 using namespace Inspector;
 
@@ -80,6 +81,7 @@ WorkerInspectorController::WorkerInspectorController(WorkerGlobalScope& workerGl
     , m_instrumentingAgents(InstrumentingAgents::create(*this))
     , m_injectedScriptManager(std::make_unique<WebInjectedScriptManager>(*this, WebInjectedScriptHost::create()))
     , m_runtimeAgent(nullptr)
+    , m_executionStopwatch(Stopwatch::create())
 {
     auto runtimeAgent = std::make_unique<WorkerRuntimeAgent>(m_injectedScriptManager.get(), &workerGlobalScope);
     m_runtimeAgent = runtimeAgent.get();
@@ -170,6 +172,11 @@ void WorkerInspectorController::didCallInjectedScriptFunction(JSC::ExecState* sc
     InspectorInstrumentation::didCallFunction(cookie, scriptExecutionContext);
 }
 
+PassRefPtr<Stopwatch> WorkerInspectorController::executionStopwatch()
+{
+    return m_executionStopwatch;
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(INSPECTOR)
index 7a21d57edfda42a8cf0dacf372e5b2740df76138..1189d37d1fe6d74315f1d7269990778d3f68c005 100644 (file)
@@ -74,6 +74,7 @@ public:
     virtual void willCallInjectedScriptFunction(JSC::ExecState*, const String& scriptName, int scriptLine) override;
     virtual void didCallInjectedScriptFunction(JSC::ExecState*) override;
     virtual void frontendInitialized() override { }
+    virtual PassRefPtr<WTF::Stopwatch> executionStopwatch() override;
 
 private:
     friend InstrumentingAgents* instrumentationForWorkerGlobalScope(WorkerGlobalScope*);
@@ -84,6 +85,7 @@ private:
     WorkerRuntimeAgent* m_runtimeAgent;
     Inspector::InspectorAgentRegistry m_agents;
     std::unique_ptr<InspectorFrontendChannel> m_frontendChannel;
+    RefPtr<WTF::Stopwatch> m_executionStopwatch;
     RefPtr<Inspector::InspectorBackendDispatcher> m_backendDispatcher;
     Vector<InspectorInstrumentationCookie, 2> m_injectedScriptInstrumentationCookies;
 };
index 3327d0c34d4eb72741d4542978c7168c065de4e9..963b7961ff1892cc84fa1f1785f3c3d3333f25b3 100644 (file)
@@ -1,3 +1,50 @@
+2014-10-10 Brian J. Burg  <burg@cs.washington.edu>
+
+        Web Inspector: timelines should not count time elapsed while paused in the debugger
+        https://bugs.webkit.org/show_bug.cgi?id=136351
+
+        Reviewed by Timothy Hatcher.
+
+        Don't update the timeline's current time when the debugger is paused.
+
+        Start and end times for timeline records are now in seconds elapsed since timeline
+        recording started, rather than milliseconds since the epoch. Also convert code that
+        tracks page/resource load timings to use elapsed times rather than timestamps.
+
+        Add a workaround to preserve compatibility with old backends. Convert legacy timestamps
+        in multiple agents to elapsed times.
+
+        * UserInterface/Controllers/FrameResourceManager.js:
+        (WebInspector.FrameResourceManager.prototype.resourceRequestWillBeSent):
+        (WebInspector.FrameResourceManager.prototype.resourceRequestWasServedFromMemoryCache):
+        (WebInspector.FrameResourceManager.prototype.resourceRequestDidReceiveResponse):
+        (WebInspector.FrameResourceManager.prototype.resourceRequestDidReceiveData):
+        (WebInspector.FrameResourceManager.prototype.resourceRequestDidFinishLoading):
+        (WebInspector.FrameResourceManager.prototype.resourceRequestDidFailLoading):
+        (WebInspector.FrameResourceManager.prototype._addNewResourceToFrame):
+        * UserInterface/Controllers/ProbeManager.js:
+        * UserInterface/Controllers/TimelineManager.js:
+        (WebInspector.TimelineManager.prototype.computeElapsedTime): Forward to the active TimelineRecording.
+        (WebInspector.TimelineManager.prototype.eventRecorded.processRecord):
+        (WebInspector.TimelineManager.prototype.eventRecorded):
+        (WebInspector.TimelineManager.prototype.pageDidLoad):
+        (WebInspector.TimelineManager.prototype._loadNewRecording):
+        * UserInterface/Models/Probe.js:
+        (WebInspector.ProbeSample):
+        * UserInterface/Models/Resource.js:
+        (WebInspector.Resource.prototype.updateForRedirectResponse):
+        (WebInspector.Resource.prototype.updateForResponse):
+        (WebInspector.Resource.prototype.increaseSize):
+        (WebInspector.Resource.prototype.markAsFinished):
+        (WebInspector.Resource.prototype.markAsFailed):
+        (WebInspector.Resource.prototype.revertMarkAsFinished):
+        * UserInterface/Models/TimelineRecording.js:
+        (WebInspector.TimelineRecording.prototype.computeElapsedTime):
+        * UserInterface/Views/TimelineContentView.js:
+        (WebInspector.TimelineContentView.prototype._debuggerPaused):
+        (WebInspector.TimelineContentView.prototype._debuggerResumed):
+
+
 2014-10-23  Jono Wells  <jonowells@apple.com>
 
         Web Inspector: Double border appearing in node sidebar in expanded items in OS X Mavericks.
index bd6b0a81d616578085b09e1245217b9141e3e579..8f3fa1dccb1a04125d093f1bd1881b3e3f42d81a 100644 (file)
@@ -184,18 +184,19 @@ WebInspector.FrameResourceManager.prototype = {
         if (this._waitingForMainFrameResourceTreePayload)
             return;
 
+        var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
         if (resource) {
             // This is an existing request which is being redirected, update the resource.
             console.assert(redirectResponse);
-            resource.updateForRedirectResponse(request.url, request.headers, timestamp);
+            resource.updateForRedirectResponse(request.url, request.headers, elapsedTime);
             return;
         }
 
         var initiatorSourceCodeLocation = this._initiatorSourceCodeLocationFromPayload(initiator);
 
         // This is a new request, make a new resource and add it to the right frame.
-        resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, request.url, type, request.method, request.headers, request.postData, timestamp, null, null, initiatorSourceCodeLocation);
+        resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, request.url, type, request.method, request.headers, request.postData, elapsedTime, null, null, initiatorSourceCodeLocation);
 
         // Associate the resource with the requestIdentifier so it can be found in future loading events.
         this._resourceRequestIdentifierMap[requestIdentifier] = resource;
@@ -230,13 +231,13 @@ WebInspector.FrameResourceManager.prototype = {
 
         console.assert(!(requestIdentifier in this._resourceRequestIdentifierMap));
 
+        var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
         var initiatorSourceCodeLocation = this._initiatorSourceCodeLocationFromPayload(initiator);
-
         var response = cachedResourcePayload.response;
-        var resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload.url, cachedResourcePayload.type, null, null, timestamp, null, null, initiatorSourceCodeLocation);
+        var resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload.url, cachedResourcePayload.type, null, null, elapsedTime, null, null, initiatorSourceCodeLocation);
         resource.markAsCached();
-        resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, timestamp);
-        resource.markAsFinished(timestamp);
+        resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime);
+        resource.markAsFinished(elapsedTime);
 
         if (cachedResourcePayload.sourceMapURL)
             WebInspector.sourceMapManager.downloadSourceMap(cachedResourcePayload.sourceMapURL, resource.url, resource);
@@ -253,6 +254,7 @@ WebInspector.FrameResourceManager.prototype = {
         if (this._waitingForMainFrameResourceTreePayload)
             return;
 
+        var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
 
         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
@@ -275,7 +277,7 @@ WebInspector.FrameResourceManager.prototype = {
         // If we haven't found an existing Resource by now, then it is a resource that was loading when the inspector
         // opened and we just missed the resourceRequestWillBeSent for it. So make a new resource and add it.
         if (!resource) {
-            resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, response.url, type, null, response.requestHeaders, timestamp, null, null);
+            resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, response.url, type, null, response.requestHeaders, elapsedTime, null, null);
 
             // Associate the resource with the requestIdentifier so it can be found in future loading events.
             this._resourceRequestIdentifierMap[requestIdentifier] = resource;
@@ -284,7 +286,7 @@ WebInspector.FrameResourceManager.prototype = {
         if (response.fromDiskCache)
             resource.markAsCached();
 
-        resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, timestamp);
+        resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime);
     },
 
     resourceRequestDidReceiveData: function(requestIdentifier, dataLength, encodedDataLength, timestamp)
@@ -296,6 +298,7 @@ WebInspector.FrameResourceManager.prototype = {
             return;
 
         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
+        var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
 
         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
@@ -303,7 +306,7 @@ WebInspector.FrameResourceManager.prototype = {
         if (!resource)
             return;
 
-        resource.increaseSize(dataLength, timestamp);
+        resource.increaseSize(dataLength, elapsedTime);
 
         if (encodedDataLength !== -1)
             resource.increaseTransferSize(encodedDataLength);
@@ -324,7 +327,8 @@ WebInspector.FrameResourceManager.prototype = {
         if (!resource)
             return;
 
-        resource.markAsFinished(timestamp);
+        var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
+        resource.markAsFinished(elapsedTime);
 
         if (sourceMapURL)
             WebInspector.sourceMapManager.downloadSourceMap(sourceMapURL, resource.url, resource);
@@ -347,7 +351,8 @@ WebInspector.FrameResourceManager.prototype = {
         if (!resource)
             return;
 
-        resource.markAsFailed(canceled, timestamp);
+        var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
+        resource.markAsFailed(canceled, elapsedTime);
 
         if (resource === resource.parentFrame.provisionalMainResource)
             resource.parentFrame.clearProvisionalLoad();
@@ -382,7 +387,7 @@ WebInspector.FrameResourceManager.prototype = {
 
     // Private
 
-    _addNewResourceToFrame: function(requestIdentifier, frameIdentifier, loaderIdentifier, url, type, requestMethod, requestHeaders, requestData, timestamp, frameName, frameSecurityOrigin, initiatorSourceCodeLocation)
+    _addNewResourceToFrame: function(requestIdentifier, frameIdentifier, loaderIdentifier, url, type, requestMethod, requestHeaders, requestData, elapsedTime, frameName, frameSecurityOrigin, initiatorSourceCodeLocation)
     {
         console.assert(!this._waitingForMainFrameResourceTreePayload);
 
@@ -396,12 +401,12 @@ WebInspector.FrameResourceManager.prototype = {
             else if (frame.provisionalMainResource && frame.provisionalMainResource.url === url && frame.provisionalLoaderIdentifier === loaderIdentifier)
                 resource = frame.provisionalMainResource;
             else {
-                resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, timestamp, initiatorSourceCodeLocation);
+                resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, elapsedTime, initiatorSourceCodeLocation);
                 this._addResourceToFrame(frame, resource);
             }
         } else {
             // This is a new request for a new frame, which is always the main resource.
-            resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, timestamp, initiatorSourceCodeLocation);
+            resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, elapsedTime, initiatorSourceCodeLocation);
             frame = new WebInspector.Frame(frameIdentifier, frameName, frameSecurityOrigin, loaderIdentifier, resource);
             this._frameIdentifierMap[frame.id] = frame;
 
index 7e289c1d8c4cb0ecef6e8617dca4542f68072880..e67ce59aff6d6832a567826c66b91b26dcf06894 100644 (file)
@@ -75,7 +75,8 @@ WebInspector.ProbeManager.prototype = {
     {
         console.assert(this._probesByIdentifier.has(sample.probeId), "Unknown probe identifier specified for sample: ", sample);
         var probe = this._probesByIdentifier.get(sample.probeId);
-        probe.addSample(new WebInspector.ProbeSample(sample.sampleId, sample.batchId, sample.timestamp, sample.payload));
+        var elapsedTime = WebInspector.timelineManager.computeElapsedTime(sample.timestamp);
+        probe.addSample(new WebInspector.ProbeSample(sample.sampleId, sample.batchId, elapsedTime, sample.payload));
     },
 
     // Private
index 90212e142568cb6b3baa419b1bf703ac85dee886..3f716422e9c5aa0e45f2ac0c029dfe49caf3a6f1 100644 (file)
@@ -121,6 +121,14 @@ WebInspector.TimelineManager.prototype = {
         this._activeRecording = null;
     },
 
+    computeElapsedTime: function(timestamp)
+    {
+        if (!this._activeRecording)
+            return 0;
+
+        return this._activeRecording.computeElapsedTime(timestamp);
+    },
+
     // Protected
 
     capturingStarted: function()
@@ -163,10 +171,8 @@ WebInspector.TimelineManager.prototype = {
 
         function processRecord(recordPayload, parentRecordPayload)
         {
-            // Convert the timestamps to seconds to match the resource timestamps.
-            var startTime = recordPayload.startTime / 1000;
-            var endTime = recordPayload.endTime / 1000;
-
+            var startTime = this.activeRecording.computeElapsedTime(recordPayload.startTime);
+            var endTime = this.activeRecording.computeElapsedTime(recordPayload.endTime);
             var callFrames = this._callFramesFromPayload(recordPayload.stackTrace);
 
             var significantCallFrame = null;
@@ -393,7 +399,7 @@ WebInspector.TimelineManager.prototype = {
     pageDidLoad: function(timestamp)
     {
         if (isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp))
-            WebInspector.frameResourceManager.mainFrame.markLoadEvent(timestamp);
+            WebInspector.frameResourceManager.mainFrame.markLoadEvent(this.activeRecording.computeElapsedTime(timestamp));
     },
 
     // Private
@@ -417,6 +423,7 @@ WebInspector.TimelineManager.prototype = {
         if (oldRecording)
             oldRecording.unloaded();
 
+        this._legacyFirstRecordedTimestamp = NaN;
         this._activeRecording = newRecording;
         this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingLoaded, {oldRecording: oldRecording});
     },
index 27b09b0689973b53d4071aa28231b35018a32321..b78b60aa0fe98667f8e2d7dea68c6c403234732e 100644 (file)
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.ProbeSample = function(sampleId, batchId, timestamp, payload)
+WebInspector.ProbeSample = function(sampleId, batchId, elapsedTime, payload)
 {
     this.sampleId = sampleId;
     this.batchId = batchId;
-    this.timestamp = timestamp;
+    this.timestamp = elapsedTime;
     this.object = WebInspector.RemoteObject.fromPayload(payload);
 };
 
index 7a245c39dc6c01970ae5441617fbe8f0d98a0d13..a05460ec384db41a040803d164c4e83fd276a949 100644 (file)
@@ -459,7 +459,7 @@ WebInspector.Resource.prototype = {
         return null;
     },
 
-    updateForRedirectResponse: function(url, requestHeaders, timestamp)
+    updateForRedirectResponse: function(url, requestHeaders, elapsedTime)
     {
         console.assert(!this._finished);
         console.assert(!this._failed);
@@ -469,7 +469,7 @@ WebInspector.Resource.prototype = {
 
         this._url = url;
         this._requestHeaders = requestHeaders || {};
-        this._lastRedirectReceivedTimestamp = timestamp || NaN;
+        this._lastRedirectReceivedTimestamp = elapsedTime || NaN;
 
         if (oldURL !== url) {
             // Delete the URL components so the URL is re-parsed the next time it is requested.
@@ -482,7 +482,7 @@ WebInspector.Resource.prototype = {
         this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
     },
 
-    updateForResponse: function(url, mimeType, type, responseHeaders, statusCode, statusText, timestamp)
+    updateForResponse: function(url, mimeType, type, responseHeaders, statusCode, statusText, elapsedTime)
     {
         console.assert(!this._finished);
         console.assert(!this._failed);
@@ -501,7 +501,7 @@ WebInspector.Resource.prototype = {
         this._statusCode = statusCode;
         this._statusText = statusText;
         this._responseHeaders = responseHeaders || {};
-        this._responseReceivedTimestamp = timestamp || NaN;
+        this._responseReceivedTimestamp = elapsedTime || NaN;
 
         this._responseHeadersSize = String(this._statusCode).length + this._statusText.length + 12; // Extra length is for "HTTP/1.1 ", " ", and "\r\n".
         for (var name in this._responseHeaders)
@@ -563,7 +563,7 @@ WebInspector.Resource.prototype = {
         return false;
     },
 
-    increaseSize: function(dataLength, timestamp)
+    increaseSize: function(dataLength, elapsedTime)
     {
         console.assert(dataLength >= 0);
 
@@ -574,7 +574,7 @@ WebInspector.Resource.prototype = {
 
         this._size += dataLength;
 
-        this._lastDataReceivedTimestamp = timestamp || NaN;
+        this._lastDataReceivedTimestamp = elapsedTime || NaN;
 
         this.dispatchEventToListeners(WebInspector.Resource.Event.SizeDidChange, {previousSize: previousSize});
 
@@ -605,13 +605,13 @@ WebInspector.Resource.prototype = {
             this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange);
     },
 
-    markAsFinished: function(timestamp)
+    markAsFinished: function(elapsedTime)
     {
         console.assert(!this._failed);
         console.assert(!this._canceled);
 
         this._finished = true;
-        this._finishedOrFailedTimestamp = timestamp || NaN;
+        this._finishedOrFailedTimestamp = elapsedTime || NaN;
 
         this.dispatchEventToListeners(WebInspector.Resource.Event.LoadingDidFinish);
         this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
@@ -620,13 +620,13 @@ WebInspector.Resource.prototype = {
             this.requestContentFromBackendIfNeeded();
     },
 
-    markAsFailed: function(canceled, timestamp)
+    markAsFailed: function(canceled, elapsedTime)
     {
         console.assert(!this._finished);
 
         this._failed = true;
         this._canceled = canceled;
-        this._finishedOrFailedTimestamp = timestamp || NaN;
+        this._finishedOrFailedTimestamp = elapsedTime || NaN;
 
         this.dispatchEventToListeners(WebInspector.Resource.Event.LoadingDidFail);
         this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
@@ -635,7 +635,7 @@ WebInspector.Resource.prototype = {
         this.servicePendingContentRequests(true);
     },
 
-    revertMarkAsFinished: function(timestamp)
+    revertMarkAsFinished: function()
     {
         console.assert(!this._failed);
         console.assert(!this._canceled);
index 285a45ff1645ec1bfc5711fb72161a4f095b5fc8..a37bb9c8bc9baf4ad07385d6ffe135074f358eba 100644 (file)
@@ -32,6 +32,9 @@ WebInspector.TimelineRecording = function(identifier, displayName)
     this._displayName = displayName;
     this._isWritable = true;
 
+    // For legacy backends, we compute the elapsed time of records relative to this timestamp.
+    this._legacyFirstRecordedTimestamp = NaN;
+
     for (var key of Object.keys(WebInspector.TimelineRecord.Type)) {
         var type = WebInspector.TimelineRecord.Type[key];
         var timeline = new WebInspector.Timeline(type);
@@ -49,6 +52,8 @@ WebInspector.TimelineRecording.Event = {
     TimesUpdated: "timeline-recording-times-updated"
 };
 
+WebInspector.TimelineRecording.TimestampThresholdForLegacyRecordConversion = 28800000; // Date.parse("Jan 1, 1970")
+
 WebInspector.TimelineRecording.prototype = {
     constructor: WebInspector.TimelineRecording,
     __proto__: WebInspector.Object.prototype,
@@ -175,6 +180,24 @@ WebInspector.TimelineRecording.prototype = {
             this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.SourceCodeTimelineAdded, {sourceCodeTimeline: sourceCodeTimeline});
     },
 
+    computeElapsedTime: function(timestamp)
+    {
+        if (!timestamp || isNaN(timestamp))
+            return NaN;
+
+        // COMPATIBILITY (iOS8): old backends send timestamps (milliseconds since the epoch), rather
+        // than seconds elapsed since timeline capturing started. We approximate the latter by
+        // subtracting the start timestamp, as old versions did not use monotonic times.
+        if (isNaN(this._legacyFirstRecordedTimestamp))
+            this._legacyFirstRecordedTimestamp = timestamp;
+
+        // If the record's start time sems unreasonably large, treat it as a legacy timestamp.
+        if (timestamp > WebInspector.TimelineRecording.TimestampThresholdForLegacyRecordConversion)
+            return (timestamp - this._legacyFirstRecordedTimestamp) / 1000.0;
+
+        return timestamp;
+    },
+
     // Private
 
     _keyForRecord: function(record)
index be4a4e7a88412bc407338e519b05bfc925b4e20d..a203f2e31fb399ceb7ed094a09f3b35ec0c9f022 100644 (file)
@@ -98,6 +98,9 @@ WebInspector.TimelineContentView = function(recording)
     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
 
+    WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerPaused, this);
+    WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerResumed, this);
+
     this.showOverviewTimelineView();
 };
 
@@ -407,6 +410,22 @@ WebInspector.TimelineContentView.prototype = {
         this._stopUpdatingCurrentTime();
     },
 
+    _debuggerPaused: function(event)
+    {
+        if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
+            return;
+
+        this._stopUpdatingCurrentTime();
+    },
+
+    _debuggerResumed: function(event)
+    {
+        if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
+            return;
+
+        this._startUpdatingCurrentTime();
+    },
+
     _recordingTimesUpdated: function(event)
     {
         if (!this._waitingToResetCurrentTime)