Web Inspector: Show rendering frames (and FPS) in Layout and Rendering timeline
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Mar 2015 08:43:30 +0000 (08:43 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Mar 2015 08:43:30 +0000 (08:43 +0000)
https://bugs.webkit.org/show_bug.cgi?id=142029

Patch by Matt Baker <mattbaker@apple.com> 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/Frames@2x.png: Added.
* UserInterface/Images/FramesLarge.png: Added.
* UserInterface/Images/FramesLarge@2x.png: 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.

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

23 files changed:
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
Source/WebInspectorUI/UserInterface/Images/Frames.png [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Images/Frames@2x.png [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Images/FramesLarge.png [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Images/FramesLarge@2x.png [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/RunLoopTimelineRecord.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Models/Timeline.js
Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js
Source/WebInspectorUI/UserInterface/Views/ContentView.js
Source/WebInspectorUI/UserInterface/Views/LayoutTimelineView.js
Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/TimelineIcons.css
Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js
Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/TimelineRecordTreeElement.js
Source/WebInspectorUI/UserInterface/Views/TimelineSidebarPanel.js
Source/WebInspectorUI/WebInspectorUI.vcxproj/WebInspectorUI.vcxproj
Source/WebInspectorUI/WebInspectorUI.vcxproj/WebInspectorUI.vcxproj.filters

index 108abce..b970314 100644 (file)
@@ -1,3 +1,109 @@
+2015-03-17  Matt Baker  <mattbaker@apple.com>
+
+        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/Frames@2x.png: Added.
+        * UserInterface/Images/FramesLarge.png: Added.
+        * UserInterface/Images/FramesLarge@2x.png: 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  <pecoraro@apple.com>
 
         Web Inspector: Add more DOM Native Function parameter strings
index df86f8e..b1467d0 100644 (file)
Binary files a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js and b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js differ
index 9701114..3f46fd9 100644 (file)
@@ -162,248 +162,268 @@ WebInspector.TimelineManager.prototype = {
         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 significantCallFrame = null;
-            if (callFrames) {
-                for (var i = 0; i < callFrames.length; ++i) {
-                    if (callFrames[i].nativeCode)
-                        continue;
-                    significantCallFrame = callFrames[i];
-                    break;
-                }
-            }
+        var records = this._processNestedRecords(recordPayload);
+        records.forEach(function(currentValue) {
+            this._addRecord(currentValue);
+        }.bind(this));
+    },
 
-            var sourceCodeLocation = significantCallFrame && significantCallFrame.sourceCodeLocation;
+    pageDidLoad: function(timestamp)
+    {
+        if (isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp))
+            WebInspector.frameResourceManager.mainFrame.markLoadEvent(this.activeRecording.computeElapsedTime(timestamp));
+    },
 
-            switch (recordPayload.type) {
-            case TimelineAgent.EventType.MarkLoad:
-                console.assert(isNaN(endTime));
+    // Private
 
-                var frame = WebInspector.frameResourceManager.frameForIdentifier(recordPayload.frameId);
-                console.assert(frame);
-                if (!frame)
-                    break;
+    _processNestedRecords: function(childRecordPayloads, parentRecordPayload)
+    {
+        // Convert to a single item array if needed.
+        if (!(childRecordPayloads instanceof Array))
+            childRecordPayloads = [childRecordPayloads];
 
-                frame.markLoadEvent(startTime);
+        var records = [];
 
-                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: childRecordPayloads, parent: parentRecordPayload || null, index: 0}];
+        while (stack.length) {
+            var entry = stack.lastValue;
+            var recordPayloads = entry.array;
 
-                var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.LoadEvent);
-                this._activeRecording.addEventMarker(eventMarker);
+            if (entry.index < recordPayloads.length) {
+                var recordPayload = recordPayloads[entry.index];
+                var record = this._processEvent(recordPayload, entry.parent);
+                if (record)
+                    records.push(record);
 
-                this._stopAutoRecordingSoon();
-                break;
+                if (recordPayload.children)
+                    stack.push({array: recordPayload.children, parent: recordPayload, index: 0});
+                ++entry.index;
+            } else
+                stack.pop();
+        }
 
-            case TimelineAgent.EventType.MarkDOMContent:
-                console.assert(isNaN(endTime));
+        return records;
+    },
 
-                var frame = WebInspector.frameResourceManager.frameForIdentifier(recordPayload.frameId);
-                console.assert(frame);
-                if (!frame)
-                    break;
+    _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;
+            }
+        }
 
-                frame.markDOMContentReadyEvent(startTime);
+        var sourceCodeLocation = significantCallFrame && significantCallFrame.sourceCodeLocation;
 
-                if (!frame.isMainFrame())
-                    break;
+        switch (recordPayload.type) {
+        case TimelineAgent.EventType.ScheduleStyleRecalculation:
+            console.assert(isNaN(endTime));
 
-                var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.DOMContentEvent);
-                this._activeRecording.addEventMarker(eventMarker);
-                break;
+            // 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);
 
-            case TimelineAgent.EventType.ScheduleStyleRecalculation:
-                console.assert(isNaN(endTime));
+        case TimelineAgent.EventType.RecalculateStyles:
+            return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.RecalculateStyles, startTime, endTime, 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.InvalidateLayout:
+            console.assert(isNaN(endTime));
 
-            case TimelineAgent.EventType.RecalculateStyles:
-                this._addRecord(new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.RecalculateStyles, startTime, endTime, callFrames, sourceCodeLocation));
-                break;
+            // 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);
 
-            case TimelineAgent.EventType.InvalidateLayout:
-                console.assert(isNaN(endTime));
+        case TimelineAgent.EventType.Layout:
+            var layoutRecordType = sourceCodeLocation ? WebInspector.LayoutTimelineRecord.EventType.ForcedLayout : WebInspector.LayoutTimelineRecord.EventType.Layout;
 
-                // 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;
+            // 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.Layout:
-                var layoutRecordType = sourceCodeLocation ? WebInspector.LayoutTimelineRecord.EventType.ForcedLayout : WebInspector.LayoutTimelineRecord.EventType.Layout;
+        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);
 
-                // 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.RunLoop:
+            if (!recordPayload.children)
+                return null;
 
-            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));
-                break;
+            var children = this._processNestedRecords(recordPayload.children, recordPayload);
+            return new WebInspector.RunLoopTimelineRecord(startTime, endTime, children);
 
-            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;
-
-                        // FIXME: No column number is provided.
-                        sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
-                    }
+        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;
+
+                    // 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 && 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;
+            break;
 
-            case TimelineAgent.EventType.TimeStamp:
-                var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.TimeStamp);
-                this._activeRecording.addEventMarker(eventMarker);
-                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.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));
+        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.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;
-
-                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);
-                    }
-                }
+            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;
 
-                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, 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;
+                    // FIXME: No column number is provided.
+                    sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
                 }
+            }
 
-                break;
+            var profileData = recordPayload.data.profile;
+
+            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);
+            }
 
-            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;
+            break;
 
-            case TimelineAgent.EventType.TimerInstall:
-                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;
+            return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ProbeSampleRecorded, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.probeId);
 
-                // 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));
-                break;
+        case TimelineAgent.EventType.TimerInstall:
+            console.assert(isNaN(endTime));
+
+            // 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);
 
-            case TimelineAgent.EventType.TimerRemove:
-                console.assert(isNaN(endTime));
+        case TimelineAgent.EventType.TimerRemove:
+            console.assert(isNaN(endTime));
 
-                // 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));
+            // 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);
+
+        case TimelineAgent.EventType.RequestAnimationFrame:
+            console.assert(isNaN(endTime));
+
+            // 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);
+
+        case TimelineAgent.EventType.CancelAnimationFrame:
+            console.assert(isNaN(endTime));
+
+            // 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);
+        }
+
+        return null;
+    },
+
+    _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.RequestAnimationFrame:
-                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.AnimationFrameRequested, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId));
+            if (!frame.isMainFrame())
                 break;
 
-            case TimelineAgent.EventType.CancelAnimationFrame:
-                console.assert(isNaN(endTime));
+            var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.LoadEvent);
+            this._activeRecording.addEventMarker(eventMarker);
+
+            this._stopAutoRecordingSoon();
+            break;
+
+        case TimelineAgent.EventType.MarkDOMContent:
+            console.assert(isNaN(endTime));
 
-                // 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));
+            var frame = WebInspector.frameResourceManager.frameForIdentifier(recordPayload.frameId);
+            console.assert(frame);
+            if (!frame)
                 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;
+            frame.markDOMContentReadyEvent(startTime);
 
-            if (entry.index < recordPayloads.length) {
-                var recordPayload = recordPayloads[entry.index];
+            if (!frame.isMainFrame())
+                break;
 
-                processRecord.call(this, recordPayload, parentRecordPayload);
+            var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.DOMContentEvent);
+            this._activeRecording.addEventMarker(eventMarker);
+            break;
 
-                if (recordPayload.children)
-                    stack.push({array: recordPayload.children, parent: recordPayload, index: 0});
-                ++entry.index;
-            } else
-                stack.pop();
+        case TimelineAgent.EventType.TimeStamp:
+            var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.TimeStamp);
+            this._activeRecording.addEventMarker(eventMarker);
+            break;
+
+        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 @@ WebInspector.TimelineManager.prototype = {
 
         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});
diff --git a/Source/WebInspectorUI/UserInterface/Images/Frames.png b/Source/WebInspectorUI/UserInterface/Images/Frames.png
new file mode 100644 (file)
index 0000000..bd43a97
Binary files /dev/null and b/Source/WebInspectorUI/UserInterface/Images/Frames.png differ
diff --git a/Source/WebInspectorUI/UserInterface/Images/Frames@2x.png b/Source/WebInspectorUI/UserInterface/Images/Frames@2x.png
new file mode 100644 (file)
index 0000000..dd6997c
Binary files /dev/null and b/Source/WebInspectorUI/UserInterface/Images/Frames@2x.png differ
diff --git a/Source/WebInspectorUI/UserInterface/Images/FramesLarge.png b/Source/WebInspectorUI/UserInterface/Images/FramesLarge.png
new file mode 100644 (file)
index 0000000..dd6997c
Binary files /dev/null and b/Source/WebInspectorUI/UserInterface/Images/FramesLarge.png differ
diff --git a/Source/WebInspectorUI/UserInterface/Images/FramesLarge@2x.png b/Source/WebInspectorUI/UserInterface/Images/FramesLarge@2x.png
new file mode 100644 (file)
index 0000000..36cc29a
Binary files /dev/null and b/Source/WebInspectorUI/UserInterface/Images/FramesLarge@2x.png differ
index 002e589..d93be67 100644 (file)
     <link rel="stylesheet" href="Views/ResourceSidebarPanel.css">
     <link rel="stylesheet" href="Views/ResourceTreeElement.css">
     <link rel="stylesheet" href="Views/RulesStyleDetailsPanel.css">
+    <link rel="stylesheet" href="Views/RunLoopTimelineOverviewGraph.css">
     <link rel="stylesheet" href="Views/ScopeBar.css">
     <link rel="stylesheet" href="Views/ScriptContentView.css">
     <link rel="stylesheet" href="Views/ScriptTimelineOverviewGraph.css">
     <link rel="stylesheet" href="Views/TimelineIcons.css">
     <link rel="stylesheet" href="Views/TimelineOverview.css">
     <link rel="stylesheet" href="Views/TimelineRecordBar.css">
+    <link rel="stylesheet" href="Views/TimelineRecordFrame.css">
     <link rel="stylesheet" href="Views/TimelineRecordingContentView.css">
     <link rel="stylesheet" href="Views/TimelineRuler.css">
     <link rel="stylesheet" href="Views/TimelineSidebarPanel.css">
     <script src="Models/ResourceCollection.js"></script>
     <script src="Models/ResourceTimelineRecord.js"></script>
     <script src="Models/Revision.js"></script>
+    <script src="Models/RunLoopTimelineRecord.js"></script>
     <script src="Models/ScopeChainNode.js"></script>
     <script src="Models/Script.js"></script>
     <script src="Models/ScriptSyntaxTree.js"></script>
     <script src="Views/ResourceTimelineDataGridNode.js"></script>
     <script src="Views/ResourceTimelineDataGridNodePathComponent.js"></script>
     <script src="Views/RulesStyleDetailsPanel.js"></script>
+    <script src="Views/RunLoopTimelineOverviewGraph.js"></script>
     <script src="Views/ScopeBar.js"></script>
     <script src="Views/ScopeBarItem.js"></script>
     <script src="Views/ScopeChainDetailsSidebarPanel.js"></script>
     <script src="Views/TextResourceContentView.js"></script>
     <script src="Views/TimelineOverview.js"></script>
     <script src="Views/TimelineRecordBar.js"></script>
+    <script src="Views/TimelineRecordFrame.js"></script>
     <script src="Views/TimelineRecordingContentView.js"></script>
     <script src="Views/TimelineRuler.js"></script>
     <script src="Views/TimelineSidebarPanel.js"></script>
diff --git a/Source/WebInspectorUI/UserInterface/Models/RunLoopTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/RunLoopTimelineRecord.js
new file mode 100644 (file)
index 0000000..e1e4004
--- /dev/null
@@ -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;
+    }
+};
index e435e99..2274381 100644 (file)
@@ -80,6 +80,8 @@ WebInspector.Timeline.prototype = {
             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 @@ WebInspector.Timeline.prototype = {
             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;
 
index 09582bc..1c003d1 100644 (file)
@@ -46,7 +46,8 @@ WebInspector.TimelineRecord.Event = {
 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";
index cb32ae7..096f3b8 100644 (file)
@@ -47,7 +47,7 @@ WebInspector.ContentView = function(representedObject)
             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)
index 05b46d5..13776eb 100644 (file)
@@ -27,7 +27,7 @@ WebInspector.LayoutTimelineView = function(timeline)
 {
     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 @@ WebInspector.LayoutTimelineView.prototype = {
     _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);
 
diff --git a/Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.css b/Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.css
new file mode 100644 (file)
index 0000000..81925e2
--- /dev/null
@@ -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
diff --git a/Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.js b/Source/WebInspectorUI/UserInterface/Views/RunLoopTimelineOverviewGraph.js
new file mode 100644 (file)
index 0000000..2d300cb
--- /dev/null
@@ -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 + "%";
+    }
+};
index 05b0ee2..5b70027 100644 (file)
@@ -63,6 +63,14 @@ body.mac-platform.legacy .colors-icon.large .icon {
     content: -webkit-image-set(url(../Images/ScriptLarge.png) 1x, url(../Images/ScriptLarge@2x.png) 2x);
 }
 
+.runloop-icon .icon {
+    content: -webkit-image-set(url(../Images/Frames.png) 1x, url(../Images/Frames@2x.png) 2x);
+}
+
+.runloop-icon.large .icon {
+    content: -webkit-image-set(url(../Images/FramesLarge.png) 1x, url(../Images/FramesLarge@2x.png) 2x);
+}
+
 .stopwatch-icon .icon {
     content: -webkit-image-set(url(../Images/Stopwatch.png) 1x, url(../Images/Stopwatch@2x.png) 2x);
 }
@@ -83,6 +91,10 @@ body.mac-platform.legacy .colors-icon.large .icon {
     content: url(../Images/TimelineRecordPaint.svg);
 }
 
+.runloop-record .icon {
+    content: -webkit-image-set(url(../Images/Frames.png) 1x, url(../Images/Frames@2x.png) 2x);
+}
+
 .evaluated-record .icon {
     content: url(../Images/TimelineRecordScriptEvaluated.svg);
 }
index 4ab1f87..8833695 100644 (file)
@@ -40,6 +40,9 @@ WebInspector.TimelineOverviewGraph = function(timeline)
         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.");
     }
 
diff --git a/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.css b/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.css
new file mode 100644 (file)
index 0000000..029b03e
--- /dev/null
@@ -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);
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js b/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js
new file mode 100644 (file)
index 0000000..99902ff
--- /dev/null
@@ -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 + "%";
+    }
+};
index 8421ba4..07d6fe9 100644 (file)
@@ -99,6 +99,11 @@ WebInspector.TimelineRecordTreeElement = function(timelineRecord, subtitleNameSt
 
         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 = function(timelineRecord, subtitleNameSt
 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";
index b664cf7..5b8c73d 100644 (file)
@@ -139,6 +139,7 @@ WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass = "stopwatch-icon";
 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";
index 0478cf4..c00d0f2 100644 (file)
     <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" />
     <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" />
     <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" />
index dda762e..b1c79dd 100644 (file)
     <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>
     <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>