Web Inspector: 80% of time during recording is spent creating source code locations...
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 30 Jul 2014 05:42:21 +0000 (05:42 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 30 Jul 2014 05:42:21 +0000 (05:42 +0000)
https://bugs.webkit.org/show_bug.cgi?id=135399

Patch by Joseph Pecoraro <pecoraro@apple.com> on 2014-07-29
Reviewed by Timothy Hatcher.

Profiling the inspector while recording a timeline, a significant amount of
time was spent creating SourceCodeLocation objects for profiling information
and call frames. A lot of this data was eagerly being processed, even though
it would not immediately be presentable to the user.

This makes two significant changes. We create a LazySourceCodeLocation class
which does as little as possible until display information is requested. We
also lazily convert the profiler payload data to a Profile object, so the
processing is only done if you drill into the Script specific timeline.

This results in a significant performance improvement in the overview view.

* UserInterface/Controllers/TimelineManager.js:
(WebInspector.TimelineManager.prototype.eventRecorded.processRecord):
(WebInspector.TimelineManager.prototype.eventRecorded):
(WebInspector.TimelineManager.prototype._callFramesFromPayload.createCallFrame):
(WebInspector.TimelineManager.prototype._callFramesFromPayload):
(WebInspector.TimelineManager.prototype._profileFromPayload.profileNodeFromPayload): Deleted.
(WebInspector.TimelineManager.prototype._profileFromPayload.profileNodeCallFromPayload): Deleted.
(WebInspector.TimelineManager.prototype._profileFromPayload): Deleted.
Do not eagerly process profile payloads. Instead pass the payload to ScriptTimelineRecords.
Likewise create callframes with lazy source code locations to do the minimum amount of work.

* UserInterface/Models/ScriptTimelineRecord.js:
(WebInspector.ScriptTimelineRecord):
(WebInspector.ScriptTimelineRecord.prototype.get profile):
(WebInspector.ScriptTimelineRecord.prototype._initializeProfileFromPayload.profileNodeFromPayload):
(WebInspector.ScriptTimelineRecord.prototype._initializeProfileFromPayload.profileNodeCallFromPayload):
(WebInspector.ScriptTimelineRecord.prototype._initializeProfileFromPayload):
Only when the profile is asked do we process the profiler payload information.
This defer the processing until at least the user drills into the Script timeline.

* UserInterface/Main.html:
Add the new class and ensure SourceCodeLocation is available beforehand.

* UserInterface/Models/LazySourceCodeLocation.js: Added.
(WebInspector.LazySourceCodeLocation):
(WebInspector.LazySourceCodeLocation.prototype.isEqual):
(WebInspector.LazySourceCodeLocation.prototype.get sourceCode):
(WebInspector.LazySourceCodeLocation.prototype.set sourceCode):
(WebInspector.LazySourceCodeLocation.prototype.get formattedLineNumber):
(WebInspector.LazySourceCodeLocation.prototype.get formattedColumnNumber):
(WebInspector.LazySourceCodeLocation.prototype.formattedPosition):
(WebInspector.LazySourceCodeLocation.prototype.hasFormattedLocation):
(WebInspector.LazySourceCodeLocation.prototype.hasDifferentDisplayLocation):
(WebInspector.LazySourceCodeLocation.prototype.resolveMappedLocation):
(WebInspector.LazySourceCodeLocation.prototype._lazyInitialization):
Only when display information is requested will we perform extra processing.
For basic information we do not need to check for formatted (pretty-printed) info.

* UserInterface/Models/SourceCode.js:
(WebInspector.SourceCode.prototype.createLazySourceCodeLocation):
Provide an explict create function for lazy source codes.

* UserInterface/Models/SourceCodeLocation.js:
(WebInspector.SourceCodeLocation.prototype.set sourceCode):
(WebInspector.SourceCodeLocation.prototype.get displaySourceCode):
(WebInspector.SourceCodeLocation.prototype.get displayLineNumber):
(WebInspector.SourceCodeLocation.prototype.get displayColumnNumber):
(WebInspector.SourceCodeLocation.prototype.hasMappedLocation):
(WebInspector.SourceCodeLocation.prototype.setSourceCode):
(WebInspector.SourceCodeLocation.prototype.resolveMappedLocation):
(WebInspector.SourceCodeLocation.prototype._makeChangeAndDispatchChangeEventIfNeeded):
(WebInspector.SourceCodeLocation.prototype._resolveMappedLocation): Deleted.
Include two protected functions which LazySourceCodeLocation overrides.

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

Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/LazySourceCodeLocation.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Models/ScriptTimelineRecord.js
Source/WebInspectorUI/UserInterface/Models/SourceCode.js
Source/WebInspectorUI/UserInterface/Models/SourceCodeLocation.js
Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js

index 239dff84f32fdb5279415714b7d7b90b186e6775..8a0a613c6487a4e4fb6a698ba32158c7aded77d4 100644 (file)
@@ -1,3 +1,76 @@
+2014-07-29  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: 80% of time during recording is spent creating source code locations for profile nodes
+        https://bugs.webkit.org/show_bug.cgi?id=135399
+
+        Reviewed by Timothy Hatcher.
+
+        Profiling the inspector while recording a timeline, a significant amount of
+        time was spent creating SourceCodeLocation objects for profiling information
+        and call frames. A lot of this data was eagerly being processed, even though
+        it would not immediately be presentable to the user.
+
+        This makes two significant changes. We create a LazySourceCodeLocation class
+        which does as little as possible until display information is requested. We
+        also lazily convert the profiler payload data to a Profile object, so the
+        processing is only done if you drill into the Script specific timeline.
+
+        This results in a significant performance improvement in the overview view.
+
+        * UserInterface/Controllers/TimelineManager.js:
+        (WebInspector.TimelineManager.prototype.eventRecorded.processRecord):
+        (WebInspector.TimelineManager.prototype.eventRecorded):
+        (WebInspector.TimelineManager.prototype._callFramesFromPayload.createCallFrame):
+        (WebInspector.TimelineManager.prototype._callFramesFromPayload):
+        (WebInspector.TimelineManager.prototype._profileFromPayload.profileNodeFromPayload): Deleted.
+        (WebInspector.TimelineManager.prototype._profileFromPayload.profileNodeCallFromPayload): Deleted.
+        (WebInspector.TimelineManager.prototype._profileFromPayload): Deleted.
+        Do not eagerly process profile payloads. Instead pass the payload to ScriptTimelineRecords.
+        Likewise create callframes with lazy source code locations to do the minimum amount of work.
+
+        * UserInterface/Models/ScriptTimelineRecord.js:
+        (WebInspector.ScriptTimelineRecord):
+        (WebInspector.ScriptTimelineRecord.prototype.get profile):
+        (WebInspector.ScriptTimelineRecord.prototype._initializeProfileFromPayload.profileNodeFromPayload):
+        (WebInspector.ScriptTimelineRecord.prototype._initializeProfileFromPayload.profileNodeCallFromPayload):
+        (WebInspector.ScriptTimelineRecord.prototype._initializeProfileFromPayload):
+        Only when the profile is asked do we process the profiler payload information.
+        This defer the processing until at least the user drills into the Script timeline.
+
+        * UserInterface/Main.html:
+        Add the new class and ensure SourceCodeLocation is available beforehand.
+
+        * UserInterface/Models/LazySourceCodeLocation.js: Added.
+        (WebInspector.LazySourceCodeLocation):
+        (WebInspector.LazySourceCodeLocation.prototype.isEqual):
+        (WebInspector.LazySourceCodeLocation.prototype.get sourceCode):
+        (WebInspector.LazySourceCodeLocation.prototype.set sourceCode):
+        (WebInspector.LazySourceCodeLocation.prototype.get formattedLineNumber):
+        (WebInspector.LazySourceCodeLocation.prototype.get formattedColumnNumber):
+        (WebInspector.LazySourceCodeLocation.prototype.formattedPosition):
+        (WebInspector.LazySourceCodeLocation.prototype.hasFormattedLocation):
+        (WebInspector.LazySourceCodeLocation.prototype.hasDifferentDisplayLocation):
+        (WebInspector.LazySourceCodeLocation.prototype.resolveMappedLocation):
+        (WebInspector.LazySourceCodeLocation.prototype._lazyInitialization):
+        Only when display information is requested will we perform extra processing.
+        For basic information we do not need to check for formatted (pretty-printed) info.
+
+        * UserInterface/Models/SourceCode.js:
+        (WebInspector.SourceCode.prototype.createLazySourceCodeLocation):
+        Provide an explict create function for lazy source codes.
+
+        * UserInterface/Models/SourceCodeLocation.js:
+        (WebInspector.SourceCodeLocation.prototype.set sourceCode):
+        (WebInspector.SourceCodeLocation.prototype.get displaySourceCode):
+        (WebInspector.SourceCodeLocation.prototype.get displayLineNumber):
+        (WebInspector.SourceCodeLocation.prototype.get displayColumnNumber):
+        (WebInspector.SourceCodeLocation.prototype.hasMappedLocation):
+        (WebInspector.SourceCodeLocation.prototype.setSourceCode):
+        (WebInspector.SourceCodeLocation.prototype.resolveMappedLocation):
+        (WebInspector.SourceCodeLocation.prototype._makeChangeAndDispatchChangeEventIfNeeded):
+        (WebInspector.SourceCodeLocation.prototype._resolveMappedLocation): Deleted.
+        Include two protected functions which LazySourceCodeLocation overrides.
+
 2014-07-29  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Only compute full ProfileNode times if needed - Legacy Profiler
index 887f86b5a9b92495cd247f910b57760da492e9fc..4e986426a314b0fc98e0c8ded7f9b87642b6820a 100644 (file)
@@ -215,16 +215,14 @@ WebInspector.TimelineManager.prototype = {
                     }
                 }
 
-                var profile = null;
-                if (recordPayload.data.profile)
-                    profile = this._profileFromPayload(recordPayload.data.profile);
+                var profileData = recordPayload.data.profile;
 
                 switch (parentRecordPayload && parentRecordPayload.type) {
                 case TimelineAgent.EventType.TimerFire:
-                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profile));
+                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData));
                     break;
                 default:
-                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, null, profile));
+                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, null, profileData));
                     break;
                 }
 
@@ -236,9 +234,9 @@ WebInspector.TimelineManager.prototype = {
                 break;
 
             case TimelineAgent.EventType.ConsoleProfile:
-                var profile = this._profileFromPayload(recordPayload.data.profile);
-                console.assert(profile);
-                this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ConsoleProfileRecorded, startTime, endTime, callFrames, sourceCodeLocation, recordPayload.data.title, profile));
+                var profileData = recordPayload.data.profile;
+                console.assert(profileData);
+                this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ConsoleProfileRecorded, startTime, endTime, callFrames, sourceCodeLocation, recordPayload.data.title, profileData));
                 break;
 
             case TimelineAgent.EventType.FunctionCall:
@@ -247,10 +245,6 @@ WebInspector.TimelineManager.prototype = {
                 if (!parentRecordPayload)
                     break;
 
-                var profile = null;
-                if (recordPayload.data.profile)
-                    profile = this._profileFromPayload(recordPayload.data.profile);
-
                 if (!sourceCodeLocation) {
                     var mainFrame = WebInspector.frameResourceManager.mainFrame;
                     var scriptResource = mainFrame.url === recordPayload.data.scriptName ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.scriptName, true);
@@ -263,21 +257,23 @@ WebInspector.TimelineManager.prototype = {
                     }
                 }
 
+                var profileData = recordPayload.data.profile;
+
                 switch (parentRecordPayload.type) {
                 case TimelineAgent.EventType.TimerFire:
-                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profile));
+                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData));
                     break;
                 case TimelineAgent.EventType.EventDispatch:
-                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profile));
+                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData));
                     break;
                 case TimelineAgent.EventType.XHRLoad:
-                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, "load", profile));
+                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, "load", profileData));
                     break;
                 case TimelineAgent.EventType.XHRReadyStateChange:
-                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, "readystatechange", profile));
+                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, "readystatechange", profileData));
                     break;
                 case TimelineAgent.EventType.FireAnimationFrame:
-                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profile));
+                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData));
                     break;
                 }
 
@@ -286,7 +282,7 @@ WebInspector.TimelineManager.prototype = {
             case TimelineAgent.EventType.ProbeSample:
                 // Pass the startTime as the endTime since this record type has no duration.
                 sourceCodeLocation = WebInspector.probeManager.probeForIdentifier(recordPayload.data.probeId).breakpoint.sourceCodeLocation;
-                this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ProbeSampleRecorded, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.probeId, profile));
+                this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ProbeSampleRecorded, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.probeId));
                 break;
 
             case TimelineAgent.EventType.TimerInstall:
@@ -348,74 +344,6 @@ WebInspector.TimelineManager.prototype = {
 
     // Private
 
-    _profileFromPayload: function(payload)
-    {
-        if (!payload)
-            return null;
-
-        console.assert(payload.rootNodes instanceof Array);
-
-        function profileNodeFromPayload(nodePayload)
-        {
-            console.assert("id" in nodePayload);
-            console.assert(nodePayload.calls instanceof Array);
-
-            if (nodePayload.url) {
-                var sourceCode = WebInspector.frameResourceManager.resourceForURL(nodePayload.url);
-                if (!sourceCode)
-                    sourceCode = WebInspector.debuggerManager.scriptsForURL(nodePayload.url)[0];
-
-                // The lineNumber is 1-based, but we expect 0-based.
-                var lineNumber = nodePayload.lineNumber - 1;
-
-                var sourceCodeLocation = sourceCode ? sourceCode.createSourceCodeLocation(lineNumber, nodePayload.columnNumber) : null;
-            }
-
-            var isProgramCode = nodePayload.functionName === "(program)";
-            var isAnonymousFunction = nodePayload.functionName === "(anonymous function)";
-
-            var type = isProgramCode ? WebInspector.ProfileNode.Type.Program : WebInspector.ProfileNode.Type.Function;
-            var functionName = !isProgramCode && !isAnonymousFunction && nodePayload.functionName !== "(unknown)" ? nodePayload.functionName : null;
-            var calls = nodePayload.calls.map(profileNodeCallFromPayload);
-
-            return new WebInspector.ProfileNode(nodePayload.id, type, functionName, sourceCodeLocation, calls, nodePayload.children);
-        }
-
-        function profileNodeCallFromPayload(nodeCallPayload)
-        {
-            console.assert("startTime" in nodeCallPayload);
-            console.assert("totalTime" in nodeCallPayload);
-
-            return new WebInspector.ProfileNodeCall(nodeCallPayload.startTime, nodeCallPayload.totalTime);
-        }
-
-        var rootNodes = payload.rootNodes;
-
-        // Iterate over the node tree using a stack. Doing this recursively can easily cause a stack overflow.
-        // We traverse the profile in post-order and convert the payloads in place until we get back to the root.
-        var stack = [{parent: {children: rootNodes}, index: 0, root: true}];
-        while (stack.length) {
-            var entry = stack.lastValue;
-
-            if (entry.index < entry.parent.children.length) {
-                var childNodePayload = entry.parent.children[entry.index];
-                if (childNodePayload.children && childNodePayload.children.length)
-                    stack.push({parent: childNodePayload, index: 0});
-
-                ++entry.index;
-            } else {
-                if (!entry.root)
-                    entry.parent.children = entry.parent.children.map(profileNodeFromPayload);
-                else
-                    rootNodes = rootNodes.map(profileNodeFromPayload);
-
-                stack.pop();
-            }
-        }
-
-        return new WebInspector.Profile(rootNodes, payload.idleTime);
-    },
-
     _callFramesFromPayload: function(payload)
     {
         if (!payload)
@@ -438,7 +366,7 @@ WebInspector.TimelineManager.prototype = {
             // The lineNumber is 1-based, but we expect 0-based.
             var lineNumber = payload.lineNumber - 1;
 
-            var sourceCodeLocation = sourceCode ? sourceCode.createSourceCodeLocation(lineNumber, payload.columnNumber) : null;
+            var sourceCodeLocation = sourceCode ? sourceCode.createLazySourceCodeLocation(lineNumber, payload.columnNumber) : null;
             var functionName = payload.functionName !== "global code" ? payload.functionName : null;
 
             return new WebInspector.CallFrame(null, sourceCodeLocation, functionName, null, null, nativeCode);
index f815d1bfc7406731252553e6b81b3d6d4c26938d..c1e2d94eaeb49937e14fe96076c1704b73fefa7e 100644 (file)
 
     <script src="Models/BreakpointAction.js"></script>
     <script src="Models/SourceCode.js"></script>
+    <script src="Models/SourceCodeLocation.js"></script>
     <script src="Models/Timeline.js"></script>
     <script src="Models/TimelineRecord.js"></script>
 
     <script src="Models/IssueMessage.js"></script>
     <script src="Models/KeyboardShortcut.js"></script>
     <script src="Models/LayoutTimelineRecord.js"></script>
+    <script src="Models/LazySourceCodeLocation.js"></script>
     <script src="Models/LogObject.js"></script>
     <script src="Models/NetworkTimeline.js"></script>
     <script src="Models/Probe.js"></script>
     <script src="Models/Script.js"></script>
     <script src="Models/ScriptTimelineRecord.js"></script>
     <script src="Models/Setting.js"></script>
-    <script src="Models/SourceCodeLocation.js"></script>
     <script src="Models/SourceCodePosition.js"></script>
     <script src="Models/SourceCodeRevision.js"></script>
     <script src="Models/SourceCodeTextRange.js"></script>
diff --git a/Source/WebInspectorUI/UserInterface/Models/LazySourceCodeLocation.js b/Source/WebInspectorUI/UserInterface/Models/LazySourceCodeLocation.js
new file mode 100644 (file)
index 0000000..48cd1fc
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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.
+ */
+
+// FIXME: Investigate folding this into SourceCodeLocation proper so it can always be as lazy as possible.
+
+// Lazily compute the full SourceCodeLocation information only when such information is needed.
+//  - raw information doesn't require initialization, we have that information
+//  - formatted information does require initialization, done by overriding public APIs.
+//  - display information does require initialization, done by overriding private funnel API resolveMappedLocation.
+
+WebInspector.LazySourceCodeLocation = function(sourceCode, lineNumber, columnNumber)
+{
+    WebInspector.SourceCodeLocation.call(this, null, lineNumber, columnNumber);
+
+    console.assert(sourceCode);
+
+    this._initialized = false;
+    this._lazySourceCode = sourceCode;
+};
+
+WebInspector.LazySourceCodeLocation.prototype = {
+    constructor: WebInspector.LazySourceCodeLocation,
+
+    // Public
+
+    isEqual: function(other)
+    {
+        if (!other)
+            return false;
+        return this._lazySourceCode === other._sourceCode && this._lineNumber === other._lineNumber && this._columnNumber === other._columnNumber;
+    },
+
+    get sourceCode()
+    {
+        return this._lazySourceCode;
+    },
+
+    set sourceCode(sourceCode)
+    {
+        // Getter and setter must be provided together.
+        this.setSourceCode(sourceCode);
+    },
+
+    get formattedLineNumber()
+    {
+        this._lazyInitialization();
+        return this._formattedLineNumber;
+    },
+
+    get formattedColumnNumber()
+    {
+        this._lazyInitialization();
+        return this._formattedColumnNumber;
+    },
+
+    formattedPosition: function()
+    {
+        this._lazyInitialization();
+        return new WebInspector.SourceCodePosition(this._formattedLineNumber, this._formattedColumnNumber);
+    },
+
+    hasFormattedLocation: function()
+    {
+        this._lazyInitialization();
+        return WebInspector.SourceCodeLocation.prototype.hasFormattedLocation.call(this);
+    },
+
+    hasDifferentDisplayLocation: function()
+    {
+        this._lazyInitialization();
+        return WebInspector.SourceCodeLocation.prototype.hasDifferentDisplayLocation.call(this);
+    },
+
+    // Protected
+
+    resolveMappedLocation: function()
+    {
+        this._lazyInitialization();
+        WebInspector.SourceCodeLocation.prototype.resolveMappedLocation.call(this);
+    },
+
+    // Private
+
+    _lazyInitialization: function()
+    {
+        if (!this._initialized) {
+            this._initialized = true;
+            this.sourceCode = this._lazySourceCode;
+        }
+    }
+};
+
+WebInspector.LazySourceCodeLocation.prototype.__proto__ = WebInspector.SourceCodeLocation.prototype;
index 0e70fca46c5c38b7042ed9384f685a1cb1456aaf..f57e74725ae6f08905be2f232895ad0bb644e4de 100644 (file)
@@ -23,7 +23,7 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.ScriptTimelineRecord = function(eventType, startTime, endTime, callFrames, sourceCodeLocation, details, profile)
+WebInspector.ScriptTimelineRecord = function(eventType, startTime, endTime, callFrames, sourceCodeLocation, details, profilePayload)
 {
     WebInspector.TimelineRecord.call(this, WebInspector.TimelineRecord.Type.Script, startTime, endTime, callFrames, sourceCodeLocation);
 
@@ -34,7 +34,8 @@ WebInspector.ScriptTimelineRecord = function(eventType, startTime, endTime, call
 
     this._eventType = eventType;
     this._details = details || "";
-    this._profile = profile || null;
+    this._profilePayload = profilePayload || null;
+    this._profile = null;
 };
 
 WebInspector.ScriptTimelineRecord.EventType = {
@@ -263,6 +264,7 @@ WebInspector.ScriptTimelineRecord.prototype = {
 
     get profile()
     {
+        this._initializeProfileFromPayload();
         return this._profile;
     },
 
@@ -272,6 +274,78 @@ WebInspector.ScriptTimelineRecord.prototype = {
 
         cookie[WebInspector.ScriptTimelineRecord.EventTypeCookieKey] = this._eventType;
         cookie[WebInspector.ScriptTimelineRecord.DetailsCookieKey] = this._details;
+    },
+
+    // Private
+
+    _initializeProfileFromPayload: function(payload)
+    {
+        if (this._profile || !this._profilePayload)
+            return;
+
+        var payload = this._profilePayload;
+
+        console.assert(payload.rootNodes instanceof Array);
+
+        function profileNodeFromPayload(nodePayload)
+        {
+            console.assert("id" in nodePayload);
+            console.assert(nodePayload.calls instanceof Array);
+
+            if (nodePayload.url) {
+                var sourceCode = WebInspector.frameResourceManager.resourceForURL(nodePayload.url);
+                if (!sourceCode)
+                    sourceCode = WebInspector.debuggerManager.scriptsForURL(nodePayload.url)[0];
+
+                // The lineNumber is 1-based, but we expect 0-based.
+                var lineNumber = nodePayload.lineNumber - 1;
+
+                var sourceCodeLocation = sourceCode ? sourceCode.createLazySourceCodeLocation(lineNumber, nodePayload.columnNumber) : null;
+            }
+
+            var isProgramCode = nodePayload.functionName === "(program)";
+            var isAnonymousFunction = nodePayload.functionName === "(anonymous function)";
+
+            var type = isProgramCode ? WebInspector.ProfileNode.Type.Program : WebInspector.ProfileNode.Type.Function;
+            var functionName = !isProgramCode && !isAnonymousFunction && nodePayload.functionName !== "(unknown)" ? nodePayload.functionName : null;
+            var calls = nodePayload.calls.map(profileNodeCallFromPayload);
+
+            return new WebInspector.ProfileNode(nodePayload.id, type, functionName, sourceCodeLocation, calls, nodePayload.children);
+        }
+
+        function profileNodeCallFromPayload(nodeCallPayload)
+        {
+            console.assert("startTime" in nodeCallPayload);
+            console.assert("totalTime" in nodeCallPayload);
+
+            return new WebInspector.ProfileNodeCall(nodeCallPayload.startTime, nodeCallPayload.totalTime);
+        }
+
+        var rootNodes = payload.rootNodes;
+
+        // Iterate over the node tree using a stack. Doing this recursively can easily cause a stack overflow.
+        // We traverse the profile in post-order and convert the payloads in place until we get back to the root.
+        var stack = [{parent: {children: rootNodes}, index: 0, root: true}];
+        while (stack.length) {
+            var entry = stack.lastValue;
+
+            if (entry.index < entry.parent.children.length) {
+                var childNodePayload = entry.parent.children[entry.index];
+                if (childNodePayload.children && childNodePayload.children.length)
+                    stack.push({parent: childNodePayload, index: 0});
+
+                ++entry.index;
+            } else {
+                if (!entry.root)
+                    entry.parent.children = entry.parent.children.map(profileNodeFromPayload);
+                else
+                    rootNodes = rootNodes.map(profileNodeFromPayload);
+
+                stack.pop();
+            }
+        }
+
+        this._profile = new WebInspector.Profile(rootNodes, payload.idleTime);
     }
 };
 
index fe267d29ac2b547db7fc2566de3d9263dbfd8275..8ef0b58dc815778d5a3a8767aca356509f8321ec 100644 (file)
@@ -142,6 +142,11 @@ WebInspector.SourceCode.prototype = {
         return new WebInspector.SourceCodeLocation(this, lineNumber, columnNumber);
     },
 
+    createLazySourceCodeLocation: function(lineNumber, columnNumber)
+    {
+        return new WebInspector.LazySourceCodeLocation(this, lineNumber, columnNumber);
+    },
+
     createSourceCodeTextRange: function(textRange)
     {
         return new WebInspector.SourceCodeTextRange(this, textRange);
index 583e132cea3ed7343815a0c03fc4de30930f337c..268c2ebbce83aaedb48c4a38c5cb5ce41976308f 100644 (file)
@@ -85,24 +85,7 @@ WebInspector.SourceCodeLocation.prototype = {
 
     set sourceCode(sourceCode)
     {
-        console.assert((this._sourceCode === null && sourceCode instanceof WebInspector.SourceCode) || (this._sourceCode instanceof WebInspector.SourceCode && sourceCode === null));
-
-        if (sourceCode === this._sourceCode)
-            return;
-
-        this._makeChangeAndDispatchChangeEventIfNeeded(function() {
-            if (this._sourceCode) {
-                this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
-                this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this);
-            }
-
-            this._sourceCode = sourceCode;
-
-            if (this._sourceCode) {
-                this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
-                this._sourceCode.addEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this);
-            }
-        });
+        this.setSourceCode(sourceCode);
     },
 
     // Raw line and column in the original source code.
@@ -146,19 +129,19 @@ WebInspector.SourceCodeLocation.prototype = {
 
     get displaySourceCode()
     {
-        this._resolveMappedLocation();
+        this.resolveMappedLocation();
         return this._mappedResource || this._sourceCode;
     },
 
     get displayLineNumber()
     {
-        this._resolveMappedLocation();
+        this.resolveMappedLocation();
         return isNaN(this._mappedLineNumber) ? this._formattedLineNumber : this._mappedLineNumber;
     },
 
     get displayColumnNumber()
     {
-        this._resolveMappedLocation();
+        this.resolveMappedLocation();
         return isNaN(this._mappedColumnNumber) ? this._formattedColumnNumber : this._mappedColumnNumber;
     },
 
@@ -196,7 +179,7 @@ WebInspector.SourceCodeLocation.prototype = {
 
     hasMappedLocation: function()
     {
-        this._resolveMappedLocation();
+        this.resolveMappedLocation();
         return this._mappedResource !== null;
     },
 
@@ -287,6 +270,65 @@ WebInspector.SourceCodeLocation.prototype = {
         }.bind(this));
     },
 
+    // Protected
+
+    setSourceCode: function(sourceCode)
+    {
+        console.assert((this._sourceCode === null && sourceCode instanceof WebInspector.SourceCode) || (this._sourceCode instanceof WebInspector.SourceCode && sourceCode === null));
+
+        if (sourceCode === this._sourceCode)
+            return;
+
+        this._makeChangeAndDispatchChangeEventIfNeeded(function() {
+            if (this._sourceCode) {
+                this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
+                this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this);
+            }
+
+            this._sourceCode = sourceCode;
+
+            if (this._sourceCode) {
+                this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
+                this._sourceCode.addEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this);
+            }
+        });
+    },
+
+    resolveMappedLocation: function()
+    {
+        if (this._mappedLocationIsResolved)
+            return;
+
+        console.assert(this._mappedResource === null);
+        console.assert(isNaN(this._mappedLineNumber));
+        console.assert(isNaN(this._mappedColumnNumber));
+
+        this._mappedLocationIsResolved = true;
+
+        if (!this._sourceCode)
+            return;
+
+        var sourceMaps = this._sourceCode.sourceMaps;
+        if (!sourceMaps.length)
+            return;
+
+        for (var i = 0; i < sourceMaps.length; ++i) {
+            var sourceMap = sourceMaps[i];
+            var entry = sourceMap.findEntry(this._lineNumber, this._columnNumber);
+            if (!entry || entry.length === 2)
+                continue;
+            console.assert(entry.length === 5);
+            var url = entry[2];
+            var sourceMapResource = sourceMap.resourceForURL(url);
+            if (!sourceMapResource)
+                return;
+            this._mappedResource = sourceMapResource;
+            this._mappedLineNumber = entry[3];
+            this._mappedColumnNumber = entry[4];
+            return;
+        }
+    },
+
     // Private
 
     _locationString: function(sourceCode, lineNumber, columnNumber, columnStyle, nameStyle, prefix)
@@ -337,41 +379,6 @@ WebInspector.SourceCodeLocation.prototype = {
         this._mappedColumnNumber = mappedColumnNumber;
     },
 
-    _resolveMappedLocation: function()
-    {
-        if (this._mappedLocationIsResolved)
-            return;
-
-        console.assert(this._mappedResource === null);
-        console.assert(isNaN(this._mappedLineNumber));
-        console.assert(isNaN(this._mappedColumnNumber));
-
-        this._mappedLocationIsResolved = true;
-
-        if (!this._sourceCode)
-            return;
-
-        var sourceMaps = this._sourceCode.sourceMaps;
-        if (!sourceMaps.length)
-            return;
-
-        for (var i = 0; i < sourceMaps.length; ++i) {
-            var sourceMap = sourceMaps[i];
-            var entry = sourceMap.findEntry(this._lineNumber, this._columnNumber);
-            if (!entry || entry.length === 2)
-                continue;
-            console.assert(entry.length === 5);
-            var url = entry[2];
-            var sourceMapResource = sourceMap.resourceForURL(url);
-            if (!sourceMapResource)
-                return;
-            this._mappedResource = sourceMapResource;
-            this._mappedLineNumber = entry[3];
-            this._mappedColumnNumber = entry[4];
-            return;
-        }
-    },
-
     _resolveFormattedLocation: function()
     {
         if (this._sourceCode && this._sourceCode.formatterSourceMap) {
@@ -402,7 +409,7 @@ WebInspector.SourceCodeLocation.prototype = {
         if (changeFunction)
             changeFunction.call(this);
 
-        this._resolveMappedLocation();
+        this.resolveMappedLocation();
         this._resolveFormattedLocation();
 
         // If the display source code is non-null then the addresses are not NaN and can be compared.
index a4ea6145d398afb507165094729ff2fdf0443d9b..e8aa7216e18ae4591c36deff65fa929f311f9630 100644 (file)
@@ -99,7 +99,7 @@ WebInspector.TimelineRecording.prototype = {
         // Add the record to the global timeline by type.
         this._timelines.get(record.type).addRecord(record);
 
-        // Netowrk records don't have source code timelines.
+        // Network records don't have source code timelines.
         if (record.type === WebInspector.TimelineRecord.Type.Network)
             return;