Title: [181626] trunk/Source/WebInspectorUI
Revision
181626
Author
[email protected]
Date
2015-03-17 01:43:30 -0700 (Tue, 17 Mar 2015)

Log Message

Web Inspector: Show rendering frames (and FPS) in Layout and Rendering timeline
https://bugs.webkit.org/show_bug.cgi?id=142029

Patch by Matt Baker <[email protected]> on 2015-03-17
Reviewed by Timothy Hatcher.

Add UI for showing runloop records and their child records as a frame histogram,
with the recording time on the x-axis and the frame duration on the y-axis. Each frame
is comprised of colored regions representing the time spent in various activities (script,
layout, etc).

Eventually the Frames timeline will replace the Layout & Rendering timeline. Until the views
for the new timeline are finalized the Layout & Rendering timeline will remain in place.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Main.html:
New string and files.

* UserInterface/Controllers/TimelineManager.js:
(WebInspector.TimelineManager.prototype.eventRecorded):
(WebInspector.TimelineManager.prototype.pageDidLoad):
(WebInspector.TimelineManager.prototype._processNestedRecords):
(WebInspector.TimelineManager.prototype._processRecord):
(WebInspector.TimelineManager.prototype._processEvent):
(WebInspector.TimelineManager.prototype._loadNewRecording):
(WebInspector.TimelineManager.prototype.eventRecorded.processRecord): Deleted.
Added support for new runloop record type and nested record handling.

* UserInterface/Images/Frames.png: Added.
* UserInterface/Images/[email protected]: Added.
* UserInterface/Images/FramesLarge.png: Added.
* UserInterface/Images/[email protected]: Added.
New images for runloop timeline overview graph and runloop tree records.

* UserInterface/Models/RunLoopTimelineRecord.js: Added.
(WebInspector.RunLoopTimelineRecord):
(WebInspector.RunLoopTimelineRecord.prototype.get children):
(WebInspector.RunLoopTimelineRecord.prototype.get durationRemainder):
(WebInspector.RunLoopTimelineRecord.prototype.durationForRecords.get var):
Extends TimelineRecord to add child records and subframe duration details.

* UserInterface/Models/Timeline.js:
(WebInspector.Timeline.prototype.get displayName):
(WebInspector.Timeline.prototype.get iconClassName):
New UI strings and icons.

* UserInterface/Models/TimelineRecord.js:
* UserInterface/Views/ContentView.js:
(WebInspector.ContentView):
* UserInterface/Views/LayoutTimelineView.js:
(WebInspector.LayoutTimelineView.prototype._layoutTimelineRecordAdded):
* UserInterface/Views/TimelineRecordTreeElement.js:
(WebInspector.TimelineRecordTreeElement):
Added support for new runloop record type.

* UserInterface/Views/RunLoopTimelineOverviewGraph.css: Added.
(.timeline-overview-graph.runloop > .divider):
(.timeline-overview-graph.runloop > .divider > span):
New styles for runloop timeline graph.

* UserInterface/Views/RunLoopTimelineOverviewGraph.js: Added.
(WebInspector.RunLoopTimelineOverviewGraph):
(WebInspector.RunLoopTimelineOverviewGraph.prototype.updateLayout.createFrame):
(WebInspector.RunLoopTimelineOverviewGraph.prototype.get graphHeightSeconds.this):
(WebInspector.RunLoopTimelineOverviewGraph.prototype.get graphHeightSeconds):
(WebInspector.RunLoopTimelineOverviewGraph.prototype._updateDividers.createDividerAtPosition.get if):
New overview graph for displaying TimelineRecordFrames and horizontal frame budget dividers.

* UserInterface/Views/TimelineIcons.css:
(.runloop-icon .icon):
(.runloop-icon.large .icon):
(.runloop-record .icon):
* UserInterface/Views/TimelineSidebarPanel.js:
New runloop icon styles.

* UserInterface/Views/TimelineOverviewGraph.js:
(WebInspector.TimelineOverviewGraph):
Updated factory to support creation of the new overview graph.

* UserInterface/Views/TimelineRecordFrame.css: Added.
(.timeline-record-frame):
(.timeline-record-frame > .frame):
(.timeline-record-frame > .dropped):
(.timeline-record-frame > .frame > .duration):
(.timeline-record-frame > .frame > .duration:first-child):
(.timeline-record-frame > .frame > .duration:last-child):
(.timeline-record-frame > .frame > .duration.timeline-record-type-network):
(.timeline-record-frame > .frame > .duration.timeline-record-type-layout):
(.timeline-record-frame > .frame > .duration.timeline-record-type-script):
New styles for frame bars in the runloop timeline graph.

* UserInterface/Views/TimelineRecordFrame.js: Added.
(WebInspector.TimelineRecordFrame):
(WebInspector.TimelineRecordFrame.createCombinedFrames):
(WebInspector.TimelineRecordFrame.prototype.get element):
(WebInspector.TimelineRecordFrame.prototype.get duration):
(WebInspector.TimelineRecordFrame.prototype.get records):
(WebInspector.TimelineRecordFrame.prototype.set records):
(WebInspector.TimelineRecordFrame.prototype._updateChildElements.createDurationElement):
New view representing a single frame within the runloop overview graph.

* WebInspectorUI.vcxproj/WebInspectorUI.vcxproj:
* WebInspectorUI.vcxproj/WebInspectorUI.vcxproj.filters:
New files.

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebInspectorUI/ChangeLog (181625 => 181626)


--- trunk/Source/WebInspectorUI/ChangeLog	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/ChangeLog	2015-03-17 08:43:30 UTC (rev 181626)
@@ -1,3 +1,109 @@
+2015-03-17  Matt Baker  <[email protected]>
+
+        Web Inspector: Show rendering frames (and FPS) in Layout and Rendering timeline
+        https://bugs.webkit.org/show_bug.cgi?id=142029
+
+        Reviewed by Timothy Hatcher.
+
+        Add UI for showing runloop records and their child records as a frame histogram,
+        with the recording time on the x-axis and the frame duration on the y-axis. Each frame
+        is comprised of colored regions representing the time spent in various activities (script,
+        layout, etc).
+
+        Eventually the Frames timeline will replace the Layout & Rendering timeline. Until the views
+        for the new timeline are finalized the Layout & Rendering timeline will remain in place.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Main.html:
+        New string and files.
+
+        * UserInterface/Controllers/TimelineManager.js:
+        (WebInspector.TimelineManager.prototype.eventRecorded):
+        (WebInspector.TimelineManager.prototype.pageDidLoad):
+        (WebInspector.TimelineManager.prototype._processNestedRecords):
+        (WebInspector.TimelineManager.prototype._processRecord):
+        (WebInspector.TimelineManager.prototype._processEvent):
+        (WebInspector.TimelineManager.prototype._loadNewRecording):
+        (WebInspector.TimelineManager.prototype.eventRecorded.processRecord): Deleted.
+        Added support for new runloop record type and nested record handling.
+
+        * UserInterface/Images/Frames.png: Added.
+        * UserInterface/Images/[email protected]: Added.
+        * UserInterface/Images/FramesLarge.png: Added.
+        * UserInterface/Images/[email protected]: Added.
+        New images for runloop timeline overview graph and runloop tree records.
+
+        * UserInterface/Models/RunLoopTimelineRecord.js: Added.
+        (WebInspector.RunLoopTimelineRecord):
+        (WebInspector.RunLoopTimelineRecord.prototype.get children):
+        (WebInspector.RunLoopTimelineRecord.prototype.get durationRemainder):
+        (WebInspector.RunLoopTimelineRecord.prototype.durationForRecords.get var):
+        Extends TimelineRecord to add child records and subframe duration details.
+
+        * UserInterface/Models/Timeline.js:
+        (WebInspector.Timeline.prototype.get displayName):
+        (WebInspector.Timeline.prototype.get iconClassName):
+        New UI strings and icons.
+
+        * UserInterface/Models/TimelineRecord.js:
+        * UserInterface/Views/ContentView.js:
+        (WebInspector.ContentView):
+        * UserInterface/Views/LayoutTimelineView.js:
+        (WebInspector.LayoutTimelineView.prototype._layoutTimelineRecordAdded):
+        * UserInterface/Views/TimelineRecordTreeElement.js:
+        (WebInspector.TimelineRecordTreeElement):
+        Added support for new runloop record type.
+
+        * UserInterface/Views/RunLoopTimelineOverviewGraph.css: Added.
+        (.timeline-overview-graph.runloop > .divider):
+        (.timeline-overview-graph.runloop > .divider > span):
+        New styles for runloop timeline graph.
+
+        * UserInterface/Views/RunLoopTimelineOverviewGraph.js: Added.
+        (WebInspector.RunLoopTimelineOverviewGraph):
+        (WebInspector.RunLoopTimelineOverviewGraph.prototype.updateLayout.createFrame):
+        (WebInspector.RunLoopTimelineOverviewGraph.prototype.get graphHeightSeconds.this):
+        (WebInspector.RunLoopTimelineOverviewGraph.prototype.get graphHeightSeconds):
+        (WebInspector.RunLoopTimelineOverviewGraph.prototype._updateDividers.createDividerAtPosition.get if):
+        New overview graph for displaying TimelineRecordFrames and horizontal frame budget dividers.
+
+        * UserInterface/Views/TimelineIcons.css:
+        (.runloop-icon .icon):
+        (.runloop-icon.large .icon):
+        (.runloop-record .icon):
+        * UserInterface/Views/TimelineSidebarPanel.js:
+        New runloop icon styles.
+
+        * UserInterface/Views/TimelineOverviewGraph.js:
+        (WebInspector.TimelineOverviewGraph):
+        Updated factory to support creation of the new overview graph.
+
+        * UserInterface/Views/TimelineRecordFrame.css: Added.
+        (.timeline-record-frame):
+        (.timeline-record-frame > .frame):
+        (.timeline-record-frame > .dropped):
+        (.timeline-record-frame > .frame > .duration):
+        (.timeline-record-frame > .frame > .duration:first-child):
+        (.timeline-record-frame > .frame > .duration:last-child):
+        (.timeline-record-frame > .frame > .duration.timeline-record-type-network):
+        (.timeline-record-frame > .frame > .duration.timeline-record-type-layout):
+        (.timeline-record-frame > .frame > .duration.timeline-record-type-script):
+        New styles for frame bars in the runloop timeline graph.
+
+        * UserInterface/Views/TimelineRecordFrame.js: Added.
+        (WebInspector.TimelineRecordFrame):
+        (WebInspector.TimelineRecordFrame.createCombinedFrames):
+        (WebInspector.TimelineRecordFrame.prototype.get element):
+        (WebInspector.TimelineRecordFrame.prototype.get duration):
+        (WebInspector.TimelineRecordFrame.prototype.get records):
+        (WebInspector.TimelineRecordFrame.prototype.set records):
+        (WebInspector.TimelineRecordFrame.prototype._updateChildElements.createDurationElement):
+        New view representing a single frame within the runloop overview graph.
+
+        * WebInspectorUI.vcxproj/WebInspectorUI.vcxproj:
+        * WebInspectorUI.vcxproj/WebInspectorUI.vcxproj.filters:
+        New files.
+
 2015-03-16  Joseph Pecoraro  <[email protected]>
 
         Web Inspector: Add more DOM Native Function parameter strings

Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (181625 => 181626)


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -397,6 +397,7 @@
 localizedStrings["Reveal in Original Resource"] = "Reveal in Original Resource";
 localizedStrings["Role"] = "Role";
 localizedStrings["Rules"] = "Rules";
+localizedStrings["Runloop Executed"] = "Runloop Executed";
 localizedStrings["Scheme"] = "Scheme";
 localizedStrings["Scope Chain"] = "Scope Chain";
 localizedStrings["Script"] = "Script";

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js (181625 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -162,248 +162,268 @@
         this.dispatchEventToListeners(WebInspector.TimelineManager.Event.CapturingStopped);
     },
 
-    eventRecorded: function(originalRecordPayload)
+    eventRecorded: function(recordPayload)
     {
         // Called from WebInspector.TimelineObserver.
 
         if (!this._isCapturing)
             return;
 
-        function processRecord(recordPayload, parentRecordPayload)
-        {
-            var startTime = this.activeRecording.computeElapsedTime(recordPayload.startTime);
-            var endTime = this.activeRecording.computeElapsedTime(recordPayload.endTime);
-            var callFrames = this._callFramesFromPayload(recordPayload.stackTrace);
+        var records = this._processNestedRecords(recordPayload);
+        records.forEach(function(currentValue) {
+            this._addRecord(currentValue);
+        }.bind(this));
+    },
 
-            var significantCallFrame = null;
-            if (callFrames) {
-                for (var i = 0; i < callFrames.length; ++i) {
-                    if (callFrames[i].nativeCode)
-                        continue;
-                    significantCallFrame = callFrames[i];
-                    break;
-                }
-            }
+    pageDidLoad: function(timestamp)
+    {
+        if (isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp))
+            WebInspector.frameResourceManager.mainFrame.markLoadEvent(this.activeRecording.computeElapsedTime(timestamp));
+    },
 
-            var sourceCodeLocation = significantCallFrame && significantCallFrame.sourceCodeLocation;
+    // Private
 
-            switch (recordPayload.type) {
-            case TimelineAgent.EventType.MarkLoad:
-                console.assert(isNaN(endTime));
+    _processNestedRecords: function(childRecordPayloads, parentRecordPayload)
+    {
+        // Convert to a single item array if needed.
+        if (!(childRecordPayloads instanceof Array))
+            childRecordPayloads = [childRecordPayloads];
 
-                var frame = WebInspector.frameResourceManager.frameForIdentifier(recordPayload.frameId);
-                console.assert(frame);
-                if (!frame)
-                    break;
+        var records = [];
 
-                frame.markLoadEvent(startTime);
+        // Iterate over the records tree using a stack. Doing this recursively has
+        // been known to cause a call stack overflow. https://webkit.org/b/79106
+        var stack = [{array: childRecordPayloads, parent: parentRecordPayload || null, index: 0}];
+        while (stack.length) {
+            var entry = stack.lastValue;
+            var recordPayloads = entry.array;
 
-                if (!frame.isMainFrame())
-                    break;
+            if (entry.index < recordPayloads.length) {
+                var recordPayload = recordPayloads[entry.index];
+                var record = this._processEvent(recordPayload, entry.parent);
+                if (record)
+                    records.push(record);
 
-                var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.LoadEvent);
-                this._activeRecording.addEventMarker(eventMarker);
+                if (recordPayload.children)
+                    stack.push({array: recordPayload.children, parent: recordPayload, index: 0});
+                ++entry.index;
+            } else
+                stack.pop();
+        }
 
-                this._stopAutoRecordingSoon();
+        return records;
+    },
+
+    _processRecord: function(recordPayload, parentRecordPayload)
+    {
+        var startTime = this.activeRecording.computeElapsedTime(recordPayload.startTime);
+        var endTime = this.activeRecording.computeElapsedTime(recordPayload.endTime);
+        var callFrames = this._callFramesFromPayload(recordPayload.stackTrace);
+
+        var significantCallFrame = null;
+        if (callFrames) {
+            for (var i = 0; i < callFrames.length; ++i) {
+                if (callFrames[i].nativeCode)
+                    continue;
+                significantCallFrame = callFrames[i];
                 break;
+            }
+        }
 
-            case TimelineAgent.EventType.MarkDOMContent:
-                console.assert(isNaN(endTime));
+        var sourceCodeLocation = significantCallFrame && significantCallFrame.sourceCodeLocation;
 
-                var frame = WebInspector.frameResourceManager.frameForIdentifier(recordPayload.frameId);
-                console.assert(frame);
-                if (!frame)
-                    break;
+        switch (recordPayload.type) {
+        case TimelineAgent.EventType.ScheduleStyleRecalculation:
+            console.assert(isNaN(endTime));
 
-                frame.markDOMContentReadyEvent(startTime);
+            // Pass the startTime as the endTime since this record type has no duration.
+            return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.InvalidateStyles, startTime, startTime, callFrames, sourceCodeLocation);
 
-                if (!frame.isMainFrame())
-                    break;
+        case TimelineAgent.EventType.RecalculateStyles:
+            return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.RecalculateStyles, startTime, endTime, callFrames, sourceCodeLocation);
 
-                var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.DOMContentEvent);
-                this._activeRecording.addEventMarker(eventMarker);
-                break;
+        case TimelineAgent.EventType.InvalidateLayout:
+            console.assert(isNaN(endTime));
 
-            case TimelineAgent.EventType.ScheduleStyleRecalculation:
-                console.assert(isNaN(endTime));
+            // Pass the startTime as the endTime since this record type has no duration.
+            return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.InvalidateLayout, startTime, startTime, callFrames, sourceCodeLocation);
 
-                // Pass the startTime as the endTime since this record type has no duration.
-                this._addRecord(new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.InvalidateStyles, startTime, startTime, callFrames, sourceCodeLocation));
-                break;
+        case TimelineAgent.EventType.Layout:
+            var layoutRecordType = sourceCodeLocation ? WebInspector.LayoutTimelineRecord.EventType.ForcedLayout : WebInspector.LayoutTimelineRecord.EventType.Layout;
 
-            case TimelineAgent.EventType.RecalculateStyles:
-                this._addRecord(new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.RecalculateStyles, startTime, endTime, callFrames, sourceCodeLocation));
-                break;
+            // COMPATIBILITY (iOS 6): Layout records did not contain area properties. This is not exposed via a quad "root".
+            var quad = recordPayload.data.root ? new WebInspector.Quad(recordPayload.data.root) : null;
+            if (quad)
+                return new WebInspector.LayoutTimelineRecord(layoutRecordType, startTime, endTime, callFrames, sourceCodeLocation, quad.points[0].x, quad.points[0].y, quad.width, quad.height, quad);
+            else
+                return new WebInspector.LayoutTimelineRecord(layoutRecordType, startTime, endTime, callFrames, sourceCodeLocation);
 
-            case TimelineAgent.EventType.InvalidateLayout:
-                console.assert(isNaN(endTime));
+        case TimelineAgent.EventType.Paint:
+            // COMPATIBILITY (iOS 6): Paint records data contained x, y, width, height properties. This became a quad "clip".
+            var quad = recordPayload.data.clip ? new WebInspector.Quad(recordPayload.data.clip) : null;
+            if (quad)
+                return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.Paint, startTime, endTime, callFrames, sourceCodeLocation, null, null, quad.width, quad.height, quad);
+            else
+                return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.Paint, startTime, endTime, callFrames, sourceCodeLocation, recordPayload.data.x, recordPayload.data.y, recordPayload.data.width, recordPayload.data.height);
 
-                // Pass the startTime as the endTime since this record type has no duration.
-                this._addRecord(new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.InvalidateLayout, startTime, startTime, callFrames, sourceCodeLocation));
-                break;
+        case TimelineAgent.EventType.RunLoop:
+            if (!recordPayload.children)
+                return null;
 
-            case TimelineAgent.EventType.Layout:
-                var layoutRecordType = sourceCodeLocation ? WebInspector.LayoutTimelineRecord.EventType.ForcedLayout : WebInspector.LayoutTimelineRecord.EventType.Layout;
+            var children = this._processNestedRecords(recordPayload.children, recordPayload);
+            return new WebInspector.RunLoopTimelineRecord(startTime, endTime, children);
 
-                // COMPATIBILITY (iOS 6): Layout records did not contain area properties. This is not exposed via a quad "root".
-                var quad = recordPayload.data.root ? new WebInspector.Quad(recordPayload.data.root) : null;
-                if (quad)
-                    this._addRecord(new WebInspector.LayoutTimelineRecord(layoutRecordType, startTime, endTime, callFrames, sourceCodeLocation, quad.points[0].x, quad.points[0].y, quad.width, quad.height, quad));
-                else
-                    this._addRecord(new WebInspector.LayoutTimelineRecord(layoutRecordType, startTime, endTime, callFrames, sourceCodeLocation));
-                break;
+        case TimelineAgent.EventType.EvaluateScript:
+            if (!sourceCodeLocation) {
+                var mainFrame = WebInspector.frameResourceManager.mainFrame;
+                var scriptResource = mainFrame.url ="" recordPayload.data.url ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.url, true);
+                if (scriptResource) {
+                    // The lineNumber is 1-based, but we expect 0-based.
+                    var lineNumber = recordPayload.data.lineNumber - 1;
 
-            case TimelineAgent.EventType.Paint:
-                // COMPATIBILITY (iOS 6): Paint records data contained x, y, width, height properties. This became a quad "clip".
-                var quad = recordPayload.data.clip ? new WebInspector.Quad(recordPayload.data.clip) : null;
-                if (quad)
-                    this._addRecord(new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.Paint, startTime, endTime, callFrames, sourceCodeLocation, null, null, quad.width, quad.height, quad));
-                else
-                    this._addRecord(new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.Paint, startTime, endTime, callFrames, sourceCodeLocation, recordPayload.data.x, recordPayload.data.y, recordPayload.data.width, recordPayload.data.height));
+                    // FIXME: No column number is provided.
+                    sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
+                }
+            }
+
+            var profileData = recordPayload.data.profile;
+
+            switch (parentRecordPayload && parentRecordPayload.type) {
+            case TimelineAgent.EventType.TimerFire:
+                return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
+            default:
+                return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, null, profileData);
+            }
+
+            break;
+
+        case TimelineAgent.EventType.ConsoleProfile:
+            var profileData = recordPayload.data.profile;
+            console.assert(profileData);
+            return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ConsoleProfileRecorded, startTime, endTime, callFrames, sourceCodeLocation, recordPayload.data.title, profileData);
+
+        case TimelineAgent.EventType.FunctionCall:
+            // FunctionCall always happens as a child of another record, and since the FunctionCall record
+            // has useful info we just make the timeline record here (combining the data from both records).
+            if (!parentRecordPayload)
                 break;
 
-            case TimelineAgent.EventType.EvaluateScript:
-                if (!sourceCodeLocation) {
-                    var mainFrame = WebInspector.frameResourceManager.mainFrame;
-                    var scriptResource = mainFrame.url ="" recordPayload.data.url ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.url, true);
-                    if (scriptResource) {
-                        // The lineNumber is 1-based, but we expect 0-based.
-                        var lineNumber = recordPayload.data.lineNumber - 1;
+            if (!sourceCodeLocation) {
+                var mainFrame = WebInspector.frameResourceManager.mainFrame;
+                var scriptResource = mainFrame.url ="" recordPayload.data.scriptName ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.scriptName, true);
+                if (scriptResource) {
+                    // The lineNumber is 1-based, but we expect 0-based.
+                    var lineNumber = recordPayload.data.scriptLine - 1;
 
-                        // FIXME: No column number is provided.
-                        sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
-                    }
+                    // FIXME: No column number is provided.
+                    sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
                 }
+            }
 
-                var profileData = 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, profileData));
-                    break;
-                default:
-                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, null, profileData));
-                    break;
-                }
+            switch (parentRecordPayload.type) {
+            case TimelineAgent.EventType.TimerFire:
+                return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
+            case TimelineAgent.EventType.EventDispatch:
+                return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData);
+            case TimelineAgent.EventType.XHRLoad:
+                return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, "load", profileData);
+            case TimelineAgent.EventType.XHRReadyStateChange:
+                return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, "readystatechange", profileData);
+            case TimelineAgent.EventType.FireAnimationFrame:
+                return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
+            }
 
-                break;
+            break;
 
-            case TimelineAgent.EventType.TimeStamp:
-                var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.TimeStamp);
-                this._activeRecording.addEventMarker(eventMarker);
-                break;
+        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;
+            return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ProbeSampleRecorded, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.probeId);
 
-            case TimelineAgent.EventType.ConsoleProfile:
-                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.TimerInstall:
+            console.assert(isNaN(endTime));
 
-            case TimelineAgent.EventType.FunctionCall:
-                // FunctionCall always happens as a child of another record, and since the FunctionCall record
-                // has useful info we just make the timeline record here (combining the data from both records).
-                if (!parentRecordPayload)
-                    break;
+            // Pass the startTime as the endTime since this record type has no duration.
+            return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerInstalled, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
 
-                if (!sourceCodeLocation) {
-                    var mainFrame = WebInspector.frameResourceManager.mainFrame;
-                    var scriptResource = mainFrame.url ="" recordPayload.data.scriptName ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.scriptName, true);
-                    if (scriptResource) {
-                        // The lineNumber is 1-based, but we expect 0-based.
-                        var lineNumber = recordPayload.data.scriptLine - 1;
+        case TimelineAgent.EventType.TimerRemove:
+            console.assert(isNaN(endTime));
 
-                        // FIXME: No column number is provided.
-                        sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
-                    }
-                }
+            // Pass the startTime as the endTime since this record type has no duration.
+            return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerRemoved, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
 
-                var profileData = recordPayload.data.profile;
+        case TimelineAgent.EventType.RequestAnimationFrame:
+            console.assert(isNaN(endTime));
 
-                switch (parentRecordPayload.type) {
-                case TimelineAgent.EventType.TimerFire:
-                    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, profileData));
-                    break;
-                case TimelineAgent.EventType.XHRLoad:
-                    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", profileData));
-                    break;
-                case TimelineAgent.EventType.FireAnimationFrame:
-                    this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData));
-                    break;
-                }
+            // Pass the startTime as the endTime since this record type has no duration.
+            return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameRequested, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
 
-                break;
+        case TimelineAgent.EventType.CancelAnimationFrame:
+            console.assert(isNaN(endTime));
 
-            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));
-                break;
+            // Pass the startTime as the endTime since this record type has no duration.
+            return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameCanceled, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
+        }
 
-            case TimelineAgent.EventType.TimerInstall:
-                console.assert(isNaN(endTime));
+        return null;
+    },
 
-                // Pass the startTime as the endTime since this record type has no duration.
-                this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerInstalled, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId));
+    _processEvent: function(recordPayload, parentRecordPayload)
+    {
+        var startTime = this.activeRecording.computeElapsedTime(recordPayload.startTime);
+        var endTime = this.activeRecording.computeElapsedTime(recordPayload.endTime);
+
+        switch (recordPayload.type) {
+        case TimelineAgent.EventType.MarkLoad:
+            console.assert(isNaN(endTime));
+
+            var frame = WebInspector.frameResourceManager.frameForIdentifier(recordPayload.frameId);
+            console.assert(frame);
+            if (!frame)
                 break;
 
-            case TimelineAgent.EventType.TimerRemove:
-                console.assert(isNaN(endTime));
+            frame.markLoadEvent(startTime);
 
-                // Pass the startTime as the endTime since this record type has no duration.
-                this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerRemoved, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId));
+            if (!frame.isMainFrame())
                 break;
 
-            case TimelineAgent.EventType.RequestAnimationFrame:
-                console.assert(isNaN(endTime));
+            var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.LoadEvent);
+            this._activeRecording.addEventMarker(eventMarker);
 
-                // Pass the startTime as the endTime since this record type has no duration.
-                this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameRequested, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId));
+            this._stopAutoRecordingSoon();
+            break;
+
+        case TimelineAgent.EventType.MarkDOMContent:
+            console.assert(isNaN(endTime));
+
+            var frame = WebInspector.frameResourceManager.frameForIdentifier(recordPayload.frameId);
+            console.assert(frame);
+            if (!frame)
                 break;
 
-            case TimelineAgent.EventType.CancelAnimationFrame:
-                console.assert(isNaN(endTime));
+            frame.markDOMContentReadyEvent(startTime);
 
-                // Pass the startTime as the endTime since this record type has no duration.
-                this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameCanceled, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId));
+            if (!frame.isMainFrame())
                 break;
-            }
-        }
 
-        // Iterate over the records tree using a stack. Doing this recursively has
-        // been known to cause a call stack overflow. https://webkit.org/b/79106
-        var stack = [{array: [originalRecordPayload], parent: null, index: 0}];
-        while (stack.length) {
-            var entry = stack.lastValue;
-            var recordPayloads = entry.array;
-            var parentRecordPayload = entry.parent;
+            var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.DOMContentEvent);
+            this._activeRecording.addEventMarker(eventMarker);
+            break;
 
-            if (entry.index < recordPayloads.length) {
-                var recordPayload = recordPayloads[entry.index];
+        case TimelineAgent.EventType.TimeStamp:
+            var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.TimeStamp);
+            this._activeRecording.addEventMarker(eventMarker);
+            break;
 
-                processRecord.call(this, recordPayload, parentRecordPayload);
-
-                if (recordPayload.children)
-                    stack.push({array: recordPayload.children, parent: recordPayload, index: 0});
-                ++entry.index;
-            } else
-                stack.pop();
+        default:
+            return this._processRecord(recordPayload, parentRecordPayload);
         }
-    },
 
-    pageDidLoad: function(timestamp)
-    {
-        if (isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp))
-            WebInspector.frameResourceManager.mainFrame.markLoadEvent(this.activeRecording.computeElapsedTime(timestamp));
+        return null;
     },
 
-    // Private
-
     _loadNewRecording: function()
     {
         if (this._activeRecording && this._activeRecording.isEmpty())
@@ -411,9 +431,10 @@
 
         var identifier = this._nextRecordingIdentifier++;
         var newRecording = new WebInspector.TimelineRecording(identifier, WebInspector.UIString("Timeline Recording %d").format(identifier));
-        newRecording.addTimeline(new WebInspector.Timeline(WebInspector.TimelineRecord.Type.Network));
-        newRecording.addTimeline(new WebInspector.Timeline(WebInspector.TimelineRecord.Type.Layout));
-        newRecording.addTimeline(new WebInspector.Timeline(WebInspector.TimelineRecord.Type.Script));
+        newRecording.addTimeline(new WebInspector.Timeline(WebInspector.TimelineRecord.Type.Network, newRecording));
+        newRecording.addTimeline(new WebInspector.Timeline(WebInspector.TimelineRecord.Type.RunLoop, newRecording));
+        newRecording.addTimeline(new WebInspector.Timeline(WebInspector.TimelineRecord.Type.Layout, newRecording));
+        newRecording.addTimeline(new WebInspector.Timeline(WebInspector.TimelineRecord.Type.Script, newRecording));
 
         this._recordings.push(newRecording);
         this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingCreated, {recording: newRecording});

Added: trunk/Source/WebInspectorUI/UserInterface/Images/Frames.png


(Binary files differ)
Property changes on: trunk/Source/WebInspectorUI/UserInterface/Images/Frames.png ___________________________________________________________________

Added: svn:mime-type

Added: trunk/Source/WebInspectorUI/UserInterface/Images/[email protected]


(Binary files differ)
Property changes on: trunk/Source/WebInspectorUI/UserInterface/Images/[email protected] ___________________________________________________________________

Added: svn:mime-type

Added: trunk/Source/WebInspectorUI/UserInterface/Images/FramesLarge.png


(Binary files differ)
Property changes on: trunk/Source/WebInspectorUI/UserInterface/Images/FramesLarge.png ___________________________________________________________________

Added: svn:mime-type

Added: trunk/Source/WebInspectorUI/UserInterface/Images/[email protected]


(Binary files differ)
Property changes on: trunk/Source/WebInspectorUI/UserInterface/Images/[email protected] ___________________________________________________________________

Added: svn:mime-type

Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (181625 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Main.html	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html	2015-03-17 08:43:30 UTC (rev 181626)
@@ -114,6 +114,7 @@
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
+    <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
@@ -134,6 +135,7 @@
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
+    <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
@@ -272,6 +274,7 @@
     <script src=""
     <script src=""
     <script src=""
+    <script src=""
     <script src=""
     <script src=""
     <script src=""
@@ -448,6 +451,7 @@
     <script src=""
     <script src=""
     <script src=""
+    <script src=""
     <script src=""
     <script src=""
     <script src=""
@@ -471,6 +475,7 @@
     <script src=""
     <script src=""
     <script src=""
+    <script src=""
     <script src=""
     <script src=""
     <script src=""

Added: trunk/Source/WebInspectorUI/UserInterface/Models/RunLoopTimelineRecord.js (0 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Models/RunLoopTimelineRecord.js	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/RunLoopTimelineRecord.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are 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.
+ */
+
+WebInspector.RunLoopTimelineRecord = function(startTime, endTime, children)
+{
+    WebInspector.TimelineRecord.call(this, WebInspector.TimelineRecord.Type.RunLoop, startTime, endTime);
+
+    this._children = children || [];
+    this._durationByRecordType = new Map;
+    this._durationRemainder = NaN;
+};
+
+WebInspector.RunLoopTimelineRecord.TypeIdentifier = "runloop-timeline-record";
+
+WebInspector.RunLoopTimelineRecord.prototype = {
+    constructor: WebInspector.RunLoopTimelineRecord,
+    __proto__: WebInspector.TimelineRecord.prototype,
+
+    // Public
+
+    get children()
+    {
+        return this._children.slice();
+    },
+
+    get durationRemainder()
+    {
+        if (!isNaN(this._durationRemainder))
+            return this._durationRemainder;
+
+        this._durationRemainder = this.duration;
+        for (recordType in WebInspector.TimelineRecord.Type)
+            this._durationRemainder -= this.durationForRecords(WebInspector.TimelineRecord.Type[recordType]);
+
+        return this._durationRemainder;
+    },
+
+    durationForRecords(recordType)
+    {
+        if (this._durationByRecordType.has(recordType))
+            return this._durationByRecordType.get(recordType);
+
+        var duration = this._children.reduce(function(previousValue, currentValue) {
+            if (currentValue.type === recordType) {
+                var currentDuration = currentValue.duration;
+                if (currentValue.usesActiveStartTime)
+                    currentDuration -= currentValue.inactiveDuration;
+                return previousValue + currentDuration;
+            }
+            return previousValue;
+        }, 0);
+
+        this._durationByRecordType.set(recordType, duration);
+        return duration;
+    }
+};

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Timeline.js (181625 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Models/Timeline.js	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Timeline.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -80,6 +80,8 @@
             return WebInspector.UIString("Layout & Rendering");
         if (this._type === WebInspector.TimelineRecord.Type.Script)
             return WebInspector.UIString("_javascript_ & Events");
+        if (this._type === WebInspector.TimelineRecord.Type.RunLoop)
+            return WebInspector.UIString("Frames");
 
         console.error("Timeline has unknown type:", this._type, this);
     },
@@ -90,6 +92,8 @@
             return WebInspector.TimelineSidebarPanel.NetworkIconStyleClass;
         if (this._type === WebInspector.TimelineRecord.Type.Layout)
             return WebInspector.TimelineSidebarPanel.ColorsIconStyleClass;
+        if (this._type === WebInspector.TimelineRecord.Type.RunLoop)
+            return WebInspector.TimelineSidebarPanel.RunLoopIconStyleClass;
         if (this._type === WebInspector.TimelineRecord.Type.Script)
             return WebInspector.TimelineSidebarPanel.ScriptIconStyleClass;
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js (181625 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -46,7 +46,8 @@
 WebInspector.TimelineRecord.Type = {
     Network: "timeline-record-type-network",
     Layout: "timeline-record-type-layout",
-    Script: "timeline-record-type-script"
+    Script: "timeline-record-type-script",
+    RunLoop: "timeline-record-type-runloop"
 };
 
 WebInspector.TimelineRecord.TypeIdentifier = "timeline-record";

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js (181625 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -47,7 +47,7 @@
             if (timelineType === WebInspector.TimelineRecord.Type.Network)
                 return new WebInspector.NetworkTimelineView(representedObject);
 
-            if (timelineType === WebInspector.TimelineRecord.Type.Layout)
+            if (timelineType === WebInspector.TimelineRecord.Type.Layout || timelineType === WebInspector.TimelineRecord.Type.RunLoop)
                 return new WebInspector.LayoutTimelineView(representedObject);
 
             if (timelineType === WebInspector.TimelineRecord.Type.Script)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineView.js (181625 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineView.js	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineView.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -27,7 +27,7 @@
 {
     WebInspector.TimelineView.call(this, timeline);
 
-    console.assert(timeline.type === WebInspector.TimelineRecord.Type.Layout);
+    console.assert(timeline.type === WebInspector.TimelineRecord.Type.Layout || WebInspector.TimelineRecord.Type.RunLoop);
 
     this.navigationSidebarTreeOutline._onselect_ = this._treeElementSelected.bind(this);
     this.navigationSidebarTreeOutline._ondeselect_ = this._treeElementDeselected.bind(this);
@@ -185,7 +185,7 @@
     _layoutTimelineRecordAdded: function(event)
     {
         var layoutTimelineRecord = event.data.record;
-        console.assert(layoutTimelineRecord instanceof WebInspector.LayoutTimelineRecord);
+        console.assert(layoutTimelineRecord instanceof WebInspector.LayoutTimelineRecord || layoutTimelineRecord instanceof WebInspector.RunLoopTimelineRecord);
 
         this._pendingRecords.push(layoutTimelineRecord);
 

Added: trunk/Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.css (0 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.css	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.css	2015-03-17 08:43:30 UTC (rev 181626)
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are 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.
+ */
+
+.timeline-overview-graph.runloop > .divider {
+    position: absolute;
+    z-index: 10;
+
+    width: 100%;
+    height: 1px;
+
+    background-color: rgba(0, 0, 0, 0.05);
+    text-align: right;
+}
+
+.timeline-overview-graph.runloop > .divider > span {
+    padding-right: 1px;
+
+    font-family: Helvetica, sans-serif;
+    font-size: 8px;
+
+    color: rgba(0, 0, 0, 0.2);
+}
\ No newline at end of file

Added: trunk/Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.js (0 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.js	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are 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.
+ */
+
+WebInspector.RunLoopTimelineOverviewGraph = function(timeline)
+{
+    WebInspector.TimelineOverviewGraph.call(this, timeline);
+
+    this.element.classList.add(WebInspector.RunLoopTimelineOverviewGraph.StyleClassName);
+
+    this._runLoopTimeline = timeline;
+    this._timelineRecordFrames = [];
+    this._graphHeightSeconds = NaN;
+    this._framesPerSecondDividers = new Map;
+
+    this._runLoopTimeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._timelineRecordAdded, this);
+
+    this.reset();
+};
+
+WebInspector.RunLoopTimelineOverviewGraph.StyleClassName = "runloop";
+WebInspector.RunLoopTimelineOverviewGraph.MaximumGraphHeightSeconds = 0.05;
+
+WebInspector.RunLoopTimelineOverviewGraph.prototype = {
+    constructor: WebInspector.RunLoopTimelineOverviewGraph,
+    __proto__: WebInspector.TimelineOverviewGraph.prototype,
+
+    // Public
+
+    get graphHeightSeconds()
+    {
+        if (!isNaN(this._graphHeightSeconds))
+            return this._graphHeightSeconds;
+
+        var maximumFrameDuration = this._runLoopTimeline.records.reduce(function(previousValue, currentValue) {
+            return Math.max(previousValue, currentValue.duration);
+        }, 0);
+
+        this._graphHeightSeconds = maximumFrameDuration * 1.1;  // Add 10% margin above frames.
+        this._graphHeightSeconds = Math.min(this._graphHeightSeconds, WebInspector.RunLoopTimelineOverviewGraph.MaximumGraphHeightSeconds);
+        return this._graphHeightSeconds;
+    },
+
+    reset()
+    {
+        WebInspector.TimelineOverviewGraph.prototype.reset.call(this);
+
+        this.element.removeChildren();
+
+        this._framesPerSecondDividers.clear();
+    },
+
+    updateLayout()
+    {
+        WebInspector.TimelineOverviewGraph.prototype.updateLayout.call(this);
+
+        var secondsPerPixel = this.timelineOverview.secondsPerPixel;
+
+        var recordFrameIndex = 0;
+
+        function createFrame(records)
+        {
+            var timelineRecordFrame = this._timelineRecordFrames[recordFrameIndex];
+            if (!timelineRecordFrame)
+                timelineRecordFrame = this._timelineRecordFrames[recordFrameIndex] = new WebInspector.TimelineRecordFrame(this, records);
+            else
+                timelineRecordFrame.records = records;
+
+            timelineRecordFrame.refresh(this);
+            if (!timelineRecordFrame.element.parentNode)
+                this.element.appendChild(timelineRecordFrame.element);
+            ++recordFrameIndex;
+        }
+
+        WebInspector.TimelineRecordFrame.createCombinedFrames(this._runLoopTimeline.records, secondsPerPixel, this, createFrame.bind(this));
+
+        // Remove the remaining unused TimelineRecordFrames.
+        for (; recordFrameIndex < this._timelineRecordFrames.length; ++recordFrameIndex) {
+            this._timelineRecordFrames[recordFrameIndex].records = null;
+            this._timelineRecordFrames[recordFrameIndex].element.remove();
+        }
+
+        this._updateDividers();
+    },
+
+    // Private
+
+    _timelineRecordAdded(event)
+    {
+        this._graphHeightSeconds = NaN;
+
+        this.needsLayout();
+    },
+
+    _updateDividers()
+    {
+        if (this.graphHeightSeconds === 0)
+            return;
+
+        var overviewGraphHeight = this.element.offsetHeight;
+
+        function createDividerAtPosition(framesPerSecond)
+        {
+            var secondsPerFrame = 1 / framesPerSecond;
+            var dividerTop = 1 - secondsPerFrame / this.graphHeightSeconds;
+            if (dividerTop < 0.01 || dividerTop >= 1)
+                return;
+
+            var divider = this._framesPerSecondDividers.get(framesPerSecond);
+            if (!divider) {
+                divider = document.createElement("div");
+                divider.classList.add("divider");
+
+                var label = document.createElement("span");
+                label.innerText = framesPerSecond + " fps";
+                divider.appendChild(label);
+
+                this.element.appendChild(divider);
+
+                this._framesPerSecondDividers.set(framesPerSecond, divider);
+            }
+
+            divider.style.marginTop = (dividerTop * overviewGraphHeight).toFixed(2) + "px";
+        }
+
+        createDividerAtPosition.call(this, 60);
+        createDividerAtPosition.call(this, 30);
+    },
+
+    _updateElementPosition(element, newPosition, property)
+    {
+        newPosition *= 100;
+        newPosition = newPosition.toFixed(2);
+
+        var currentPosition = parseFloat(element.style[property]).toFixed(2);
+        if (currentPosition !== newPosition)
+            element.style[property] = newPosition + "%";
+    }
+};

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineIcons.css (181625 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineIcons.css	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineIcons.css	2015-03-17 08:43:30 UTC (rev 181626)
@@ -63,6 +63,14 @@
     content: -webkit-image-set(url(../Images/ScriptLarge.png) 1x, url(../Images/[email protected]) 2x);
 }
 
+.runloop-icon .icon {
+    content: -webkit-image-set(url(../Images/Frames.png) 1x, url(../Images/[email protected]) 2x);
+}
+
+.runloop-icon.large .icon {
+    content: -webkit-image-set(url(../Images/FramesLarge.png) 1x, url(../Images/[email protected]) 2x);
+}
+
 .stopwatch-icon .icon {
     content: -webkit-image-set(url(../Images/Stopwatch.png) 1x, url(../Images/[email protected]) 2x);
 }
@@ -83,6 +91,10 @@
     content: url(../Images/TimelineRecordPaint.svg);
 }
 
+.runloop-record .icon {
+    content: -webkit-image-set(url(../Images/Frames.png) 1x, url(../Images/[email protected]) 2x);
+}
+
 .evaluated-record .icon {
     content: url(../Images/TimelineRecordScriptEvaluated.svg);
 }

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js (181625 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -40,6 +40,9 @@
         if (timelineType === WebInspector.TimelineRecord.Type.Script)
             return new WebInspector.ScriptTimelineOverviewGraph(timeline);
 
+        if (timelineType === WebInspector.TimelineRecord.Type.RunLoop)
+            return new WebInspector.RunLoopTimelineOverviewGraph(timeline);
+
         throw Error("Can't make a graph for an unknown timeline.");
     }
 

Added: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.css (0 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.css	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.css	2015-03-17 08:43:30 UTC (rev 181626)
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are 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.
+ */
+
+.timeline-record-frame {
+    position: absolute;
+    height: 36px;
+    min-width: 6px;
+
+    overflow: hidden;
+
+    -webkit-mask-image: -webkit-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 1) 10%);
+}
+
+.timeline-record-frame > .frame {
+    position: absolute;
+    z-index: 1;
+    bottom: 1px;
+    min-width: 6px;
+
+    box-sizing: border-box;
+}
+
+.timeline-record-frame > .dropped {
+    position: absolute;
+    bottom: 1px;
+    right: 0;
+
+    min-width: 6px;
+    box-sizing: border-box;
+
+    background: repeating-linear-gradient(-45deg, white, white 2px, rgb(234, 234, 234) 2px, rgb(234, 234, 234) 4px);
+
+    border-top-style: solid;
+    border-right-style: solid;
+    border-width: 1px;
+    border-color: rgb(221, 221, 221);
+}
+
+.timeline-record-frame > .frame > .duration {
+    box-sizing: border-box;
+
+    background-color: rgb(221, 221, 221);
+
+    border-color: rgb(176, 176, 176);
+    border-style: none solid solid solid;
+    border-width: 1px;
+}
+
+.timeline-record-frame > .frame > .duration:first-child {
+    border-top-style: solid;
+}
+
+.timeline-record-frame > .frame > .duration:last-child {
+    border-bottom-style: none;
+}
+
+.timeline-record-frame > .frame > .duration.timeline-record-type-network {
+    background-color: rgb(120, 176, 225);
+    border-color: rgb(61, 147, 200);
+}
+
+.timeline-record-frame > .frame > .duration.timeline-record-type-layout {
+    background-color: rgb(234, 153, 153);
+    border-color: rgb(212, 108, 108);
+}
+
+.timeline-record-frame > .frame > .duration.timeline-record-type-script {
+    background-color: rgb(190, 148, 233);
+    border-color: rgb(153, 113, 185);
+}

Added: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js (0 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are 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.
+ */
+
+WebInspector.TimelineRecordFrame = function(graphDataSource, records)
+{
+    WebInspector.Object.call(this);
+
+    this._element = document.createElement("div");
+    this._element.classList.add(WebInspector.TimelineRecordFrame.StyleClassName);
+
+    this._graphDataSource = graphDataSource;
+    this.records = records || [];
+};
+
+WebInspector.Object.addConstructorFunctions(WebInspector.TimelineRecordFrame);
+
+WebInspector.TimelineRecordFrame.StyleClassName = "timeline-record-frame";
+WebInspector.TimelineRecordFrame.SixtyFpsFrameBudget = 0.0166;
+WebInspector.TimelineRecordFrame.MinimumWidthPixels = 6;
+WebInspector.TimelineRecordFrame.MinimumMarginPixels = 1;
+
+WebInspector.TimelineRecordFrame.createCombinedFrames = function(records, secondsPerPixel, graphDataSource, createFrameCallback)
+{
+    if (!records.length)
+        return;
+
+    var startTime = graphDataSource.startTime;
+    var currentTime = graphDataSource.currentTime;
+    var endTime = graphDataSource.endTime;
+
+    var visibleRecords = [];
+
+    for (var record of records) {
+        if (isNaN(record.startTime))
+            continue;
+
+        // If this frame is completely before the bounds of the graph, skip this record.
+        if (record.endTime < startTime)
+            continue;
+
+        // If this record is completely after the current time or end time, break out now.
+        // Records are sorted, so all records after this will be beyond the current or end time too.
+        if (record.startTime > currentTime || record.startTime > endTime)
+            break;
+
+        visibleRecords.push(record);
+    }
+
+    if (!visibleRecords.length)
+        return;
+
+    var minimumFrameDuration = secondsPerPixel * WebInspector.TimelineRecordFrame.MinimumWidthPixels;
+    var minimumMargin = secondsPerPixel * WebInspector.TimelineRecordFrame.MinimumMarginPixels;
+
+    var activeStartTime = NaN;
+    var activeEndTime = NaN;
+    var activeRecords = [];
+
+    for (var record of visibleRecords) {
+        // Check if the previous record is far enough away to create the frame.
+        if (!isNaN(activeStartTime) && (activeStartTime + Math.max(activeEndTime - activeStartTime, minimumFrameDuration) + minimumMargin <= record.startTime)) {
+            createFrameCallback(activeRecords);
+            activeRecords = [];
+            activeStartTime = NaN;
+            activeEndTime = NaN;
+        }
+
+        // If this is a new frame, peg the start time.
+        if (isNaN(activeStartTime))
+            activeStartTime = record.startTime;
+
+        // Update the end time to be the maximum we encounter. endTime might be NaN, so "|| 0" to prevent Math.max from returning NaN.
+        if (!isNaN(record.endTime))
+            activeEndTime = Math.max(activeEndTime || 0, record.endTime);
+
+        activeRecords.push(record);
+    }
+
+    // Create the active frame for the last record if needed.
+    if (!isNaN(activeStartTime))
+        createFrameCallback(activeRecords);
+};
+
+WebInspector.TimelineRecordFrame.prototype = {
+    constructor: WebInspector.TimelineRecordFrame,
+    __proto__: WebInspector.Object.prototype,
+
+    // Public
+
+    get element()
+    {
+        return this._element;
+    },
+
+    get duration()
+    {
+        if (this.records.length === 0)
+            return 0;
+
+        return this.records.lastValue.endTime - this.records[0].startTime;
+    },
+
+    get records()
+    {
+        return this._records;
+    },
+
+    set records(records)
+    {
+        records = records || [];
+
+        if (!(records instanceof Array))
+            records = [records];
+
+        this._records = records;
+    },
+
+    refresh(graphDataSource)
+    {
+        if (!this.records || !this.records.length)
+            return false;
+
+        var firstRecord = this.records[0];
+        var frameStartTime = firstRecord.startTime;
+
+        // If this frame has no time info, return early.
+        if (isNaN(frameStartTime))
+            return false;
+
+        var graphStartTime = graphDataSource.startTime;
+        var graphEndTime = graphDataSource.endTime;
+        var graphCurrentTime = graphDataSource.currentTime;
+
+        // If this frame is completely after the current time, return early.
+        if (frameStartTime > graphCurrentTime)
+            return false;
+
+        var frameEndTime = this.records.lastValue.endTime;
+
+        // If this frame is completely before or after the bounds of the graph, return early.
+        if (frameEndTime < graphStartTime || frameStartTime > graphEndTime)
+            return false;
+
+        var graphDuration = graphEndTime - graphStartTime;
+        var recordLeftPosition = (frameStartTime - graphStartTime) / graphDuration;
+        this._updateElementPosition(this._element, recordLeftPosition, "left");
+
+        var recordWidth = ((frameEndTime - graphStartTime) / graphDuration) - recordLeftPosition;
+        this._updateElementPosition(this._element, recordWidth, "width");
+
+        this._updateChildElements(graphDataSource);
+
+        return true;
+    },
+
+    // Private
+
+    _updateChildElements(graphDataSource)
+    {
+        this._element.removeChildren();
+
+        console.assert(this.records.length);
+        if (!this.records.length)
+            return;
+
+        if (graphDataSource.graphHeightSeconds === 0)
+            return;
+
+        // When combining multiple records into a frame, display the record with the longest duration rather than averaging.
+        var displayRecord = this.records.reduce(function(previousValue, currentValue) {
+            return currentValue.duration >= previousValue.duration ? currentValue : previousValue;
+        });
+
+        var frameElement = document.createElement("div");
+        frameElement.classList.add("frame");
+        this._element.appendChild(frameElement);
+
+        var frameHeight = displayRecord.duration / graphDataSource.graphHeightSeconds;
+        this._updateElementPosition(frameElement, frameHeight, "height");
+
+        function createDurationElement(duration, recordType)
+        {
+            var element = document.createElement("div");
+            this._updateElementPosition(element, duration / displayRecord.duration, "height");
+            element.classList.add("duration");
+            if (recordType)
+                element.classList.add(recordType);
+            return element;
+        }
+
+        for (type in WebInspector.TimelineRecord.Type) {
+            var recordType = WebInspector.TimelineRecord.Type[type];
+            var duration = displayRecord.durationForRecords(recordType);
+            if (duration === 0)
+                continue;
+            frameElement.appendChild(createDurationElement.call(this, duration, recordType));
+        }
+
+        if (displayRecord.durationRemainder > 0)
+            frameElement.appendChild(createDurationElement.call(this, displayRecord.durationRemainder));
+
+        // Add "includes-dropped" style class if multiple records are being combined in the frame,
+        // and one of those records exceeds the 60 fps frame budget.
+        if (this.records.length > 1 && displayRecord.duration > WebInspector.TimelineRecordFrame.SixtyFpsFrameBudget)
+            frameElement.classList.add("includes-dropped");
+
+        // If the display record is the last combined record and also exceeds the 60 fps budget,
+        // add a "dropped" element to the right of the frame.
+        var frameWidth = 1;
+        var secondsPerPixel = this._graphDataSource.timelineOverview.secondsPerPixel;
+        var minimumRecordDuration = secondsPerPixel * WebInspector.TimelineRecordFrame.MinimumWidthPixels * 2;    // Combine minimum widths of the frame element and dropped element.
+
+        if (displayRecord === this.records.lastValue && displayRecord.duration > WebInspector.TimelineRecordFrame.SixtyFpsFrameBudget && displayRecord.duration >= minimumRecordDuration) {
+            var overflowDuration = displayRecord.duration - WebInspector.TimelineRecordFrame.SixtyFpsFrameBudget;
+            var droppedElementWidth = overflowDuration / displayRecord.duration;
+            frameWidth -= droppedElementWidth;
+
+            var droppedElement = document.createElement("div");
+            droppedElement.className = "dropped";
+
+            this._element.appendChild(droppedElement);
+
+            this._updateElementPosition(droppedElement, frameHeight, "height");
+            this._updateElementPosition(droppedElement, droppedElementWidth, "width");
+        }
+
+        this._updateElementPosition(frameElement, frameWidth, "width");
+    },
+
+    _updateElementPosition(element, newPosition, property)
+    {
+        newPosition *= 100;
+        newPosition = newPosition.toFixed(2);
+
+        var currentPosition = parseFloat(element.style[property]).toFixed(2);
+        if (currentPosition !== newPosition)
+            element.style[property] = newPosition + "%";
+    }
+};

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordTreeElement.js (181625 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordTreeElement.js	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordTreeElement.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -99,6 +99,11 @@
 
         break;
 
+    case WebInspector.TimelineRecord.Type.RunLoop:
+        title = WebInspector.UIString("Runloop Executed");
+        iconStyleClass = WebInspector.TimelineRecordTreeElement.RunLoopRecordIconStyleClass;
+        break;
+
     default:
         console.error("Unknown TimelineRecord type: " + timelineRecord.type, timelineRecord);
     }
@@ -114,6 +119,7 @@
 WebInspector.TimelineRecordTreeElement.StyleRecordIconStyleClass = "style-record";
 WebInspector.TimelineRecordTreeElement.LayoutRecordIconStyleClass = "layout-record";
 WebInspector.TimelineRecordTreeElement.PaintRecordIconStyleClass = "paint-record";
+WebInspector.TimelineRecordTreeElement.RunLoopRecordIconStyleClass = "runloop-record";
 WebInspector.TimelineRecordTreeElement.EvaluatedRecordIconStyleClass = "evaluated-record";
 WebInspector.TimelineRecordTreeElement.EventRecordIconStyleClass = "event-record";
 WebInspector.TimelineRecordTreeElement.TimerRecordIconStyleClass = "timer-record";

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineSidebarPanel.js (181625 => 181626)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineSidebarPanel.js	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineSidebarPanel.js	2015-03-17 08:43:30 UTC (rev 181626)
@@ -139,6 +139,7 @@
 WebInspector.TimelineSidebarPanel.NetworkIconStyleClass = "network-icon";
 WebInspector.TimelineSidebarPanel.ColorsIconStyleClass = "colors-icon";
 WebInspector.TimelineSidebarPanel.ScriptIconStyleClass = "script-icon";
+WebInspector.TimelineSidebarPanel.RunLoopIconStyleClass = "runloop-icon";
 WebInspector.TimelineSidebarPanel.TimelineRecordingContentViewShowingStyleClass = "timeline-recording-content-view-showing";
 
 WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey = "timeline-sidebar-panel-showing-timeline-recording-content-view";

Modified: trunk/Source/WebInspectorUI/WebInspectorUI.vcxproj/WebInspectorUI.vcxproj (181625 => 181626)


--- trunk/Source/WebInspectorUI/WebInspectorUI.vcxproj/WebInspectorUI.vcxproj	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/WebInspectorUI.vcxproj/WebInspectorUI.vcxproj	2015-03-17 08:43:30 UTC (rev 181626)
@@ -525,6 +525,10 @@
     <None Include="..\UserInterface\Images\FolderGeneric.png" />
     <None Include="..\UserInterface\Images\ForwardArrow.svg" />
     <None Include="..\UserInterface\Images\ForwardArrowLegacy.svg" />
+    <None Include="..\UserInterface\Images\Frames.png" />
+    <None Include="..\UserInterface\Images\Frames%402x.png" />
+    <None Include="..\UserInterface\Images\FramesLarge.png" />
+    <None Include="..\UserInterface\Images\FramesLarge%402x.png" />
     <None Include="..\UserInterface\Images\Function.svg" />
     <None Include="..\UserInterface\Images\GoToArrow.svg" />
     <None Include="..\UserInterface\Images\HierarchicalNavigationItemChevron.svg" />
@@ -747,6 +751,9 @@
     <None Include="..\UserInterface\Revision.js" />
     <None Include="..\UserInterface\RulesStyleDetailsPanel.css" />
     <None Include="..\UserInterface\RulesStyleDetailsPanel.js" />
+    <None Include="..\UserInterface\RunLoopTimelineOverviewGraph.css" />
+    <None Include="..\UserInterface\RunLoopTimelineOverviewGraph.js" />
+    <None Include="..\UserInterface\RunLoopTimelineRecord.js" />
     <None Include="..\UserInterface\RuntimeObserver.js" />
     <None Include="..\UserInterface\ScopeBar.css" />
     <None Include="..\UserInterface\ScopeBar.js" />
@@ -808,6 +815,8 @@
     <None Include="..\UserInterface\TimelineOverview.css" />
     <None Include="..\UserInterface\TimelineOverview.js" />
     <None Include="..\UserInterface\TimelineRecord.js" />
+    <None Include="..\UserInterface\TimelineRecordFrame.css" />
+    <None Include="..\UserInterface\TimelineRecordFrame.js" />
     <None Include="..\UserInterface\TimelinesContentView.css" />
     <None Include="..\UserInterface\TimelinesContentView.js" />
     <None Include="..\UserInterface\TimelinesObject.js" />

Modified: trunk/Source/WebInspectorUI/WebInspectorUI.vcxproj/WebInspectorUI.vcxproj.filters (181625 => 181626)


--- trunk/Source/WebInspectorUI/WebInspectorUI.vcxproj/WebInspectorUI.vcxproj.filters	2015-03-17 08:41:19 UTC (rev 181625)
+++ trunk/Source/WebInspectorUI/WebInspectorUI.vcxproj/WebInspectorUI.vcxproj.filters	2015-03-17 08:43:30 UTC (rev 181626)
@@ -825,6 +825,15 @@
     <None Include="..\UserInterface\RulesStyleDetailsPanel.js">
       <Filter>UserInterface</Filter>
     </None>
+    <None Include="..\UserInterface\RunLoopTimelineOverviewGraph.css">
+      <Filter>UserInterface</Filter>
+    </None>
+    <None Include="..\UserInterface\RunLoopTimelineOverviewGraph.js">
+      <Filter>UserInterface</Filter>
+    </None>
+    <None Include="..\UserInterface\RunLoopTimelineRecord.js">
+      <Filter>UserInterface</Filter>
+    </None>
     <None Include="..\UserInterface\RuntimeObserver.js">
       <Filter>UserInterface</Filter>
     </None>
@@ -1008,6 +1017,12 @@
     <None Include="..\UserInterface\TimelineRecord.js">
       <Filter>UserInterface</Filter>
     </None>
+    <None Include="..\UserInterface\TimelineRecordFrame.css">
+      <Filter>UserInterface</Filter>
+    </None>
+    <None Include="..\UserInterface\TimelineRecordFrame.js">
+      <Filter>UserInterface</Filter>
+    </None>
     <None Include="..\UserInterface\TimelinesContentView.css">
       <Filter>UserInterface</Filter>
     </None>
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to