Web Inspector: CPU Usage Timeline - Add legend and graph hover effects
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Mar 2019 21:26:47 +0000 (21:26 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Mar 2019 21:26:47 +0000 (21:26 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195390

Reviewed by Devin Rousso.

* Localizations/en.lproj/localizedStrings.js:
New strings for the legends.

* UserInterface/Main.html:
Combined files.

* UserInterface/Views/Variables.css:
(:root):
(@media (prefers-color-scheme: dark)):
Tweaked colors, including individual stroke and fill colors for each CPU section.

* UserInterface/Views/CPUTimelineOverviewGraph.css:
(.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.total-usage):
(.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.main-thread-usage):
(.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.worker-thread-usage):
Updated colors.

* UserInterface/Views/CPUUsageCombinedView.css: Renamed from Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.css.
(.cpu-usage-combined-view > .details > .legend-container):
(.cpu-usage-combined-view > .details > .legend-container > .row):
(.cpu-usage-combined-view > .details > .legend-container > .row + .row):
(.cpu-usage-combined-view > .details > .legend-container > .row > .swatch):
* UserInterface/Views/CPUUsageCombinedView.js: Renamed from Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.js.
(WI.CPUUsageCombinedView.appendLegendRow):
(WI.CPUUsageCombinedView):
(WI.CPUUsageCombinedView.prototype.get graphElement):
(WI.CPUUsageCombinedView.prototype.get chart):
(WI.CPUUsageCombinedView.prototype.get rangeChart):
(WI.CPUUsageCombinedView.prototype.clear):
(WI.CPUUsageCombinedView.prototype.updateChart):
(WI.CPUUsageCombinedView.prototype.updateMainThreadIndicator):
(WI.CPUUsageCombinedView.prototype.clearLegend):
(WI.CPUUsageCombinedView.prototype.updateLegend):
(WI.CPUUsageCombinedView.prototype._updateDetails):
* UserInterface/Views/CPUUsageIndicatorView.css: Removed.
* UserInterface/Views/CPUUsageIndicatorView.js: Removed.
Combined the Indicator and StackedAreaChart into a single view
that share a left details section.

* UserInterface/Views/CPUUsageView.js:
(WI.CPUUsageView):
(WI.CPUUsageView.prototype.get graphElement):
(WI.CPUUsageView.prototype.clear):
(WI.CPUUsageView.prototype.updateChart):
(WI.CPUUsageView.prototype.clearLegend):
(WI.CPUUsageView.prototype.updateLegend):
(WI.CPUUsageView.prototype._updateDetails):
Include a legend in the left details section.

* UserInterface/Views/AreaChart.js:
(WI.AreaChart):
(WI.AreaChart.prototype.addPointMarker):
(WI.AreaChart.prototype.clearPointMarkers):
(WI.AreaChart.prototype.clear):
(WI.AreaChart.prototype.layout):
* UserInterface/Views/StackedAreaChart.js:
(WI.StackedAreaChart):
(WI.StackedAreaChart.prototype.addPointMarker):
(WI.StackedAreaChart.prototype.clearPointMarkers):
(WI.StackedAreaChart.prototype.clear):
(WI.StackedAreaChart.prototype.layout):
Add point markers for the area charts.

* UserInterface/Views/CPUTimelineView.css:
* UserInterface/Views/CPUTimelineView.js:
(WI.CPUTimelineView):
(WI.CPUTimelineView.prototype.get cpuUsageViewHeight):
(WI.CPUTimelineView.prototype.clear):
(WI.CPUTimelineView.prototype.initialLayout.appendLegendRow):
(WI.CPUTimelineView.prototype.initialLayout):
(WI.CPUTimelineView.prototype.layout):
(WI.CPUTimelineView.prototype._graphPositionForMouseEvent):
(WI.CPUTimelineView.prototype._handleMouseClick):
(WI.CPUTimelineView.prototype._handleGraphMouseMove):
(WI.CPUTimelineView.prototype._showGraphOverlayNearTo):
(WI.CPUTimelineView.prototype._updateGraphOverlay):
(WI.CPUTimelineView.prototype._showGraphOverlay.xScale):
(WI.CPUTimelineView.prototype._showGraphOverlay.yScale):
(WI.CPUTimelineView.prototype._showGraphOverlay.addOverlayPoint):
(WI.CPUTimelineView.prototype._showGraphOverlay):
(WI.CPUTimelineView.prototype._clearOverlayMarkers.clearGraphOverlayElement):
(WI.CPUTimelineView.prototype._clearOverlayMarkers):
(WI.CPUTimelineView.prototype._hideGraphOverlay):
Include graph overlay markers.

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

15 files changed:
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Views/AreaChart.js
Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css
Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css
Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js
Source/WebInspectorUI/UserInterface/Views/CPUUsageCombinedView.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/CPUUsageCombinedView.js [moved from Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.js with 50% similarity]
Source/WebInspectorUI/UserInterface/Views/CPUUsageIndicatorView.css [deleted file]
Source/WebInspectorUI/UserInterface/Views/CPUUsageIndicatorView.js [deleted file]
Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.css [deleted file]
Source/WebInspectorUI/UserInterface/Views/CPUUsageView.js
Source/WebInspectorUI/UserInterface/Views/StackedAreaChart.js
Source/WebInspectorUI/UserInterface/Views/Variables.css

index c38214b..941e306 100644 (file)
@@ -1,3 +1,95 @@
+2019-03-11  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: CPU Usage Timeline - Add legend and graph hover effects
+        https://bugs.webkit.org/show_bug.cgi?id=195390
+
+        Reviewed by Devin Rousso.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        New strings for the legends.
+
+        * UserInterface/Main.html:
+        Combined files.
+
+        * UserInterface/Views/Variables.css:
+        (:root):
+        (@media (prefers-color-scheme: dark)):
+        Tweaked colors, including individual stroke and fill colors for each CPU section.
+
+        * UserInterface/Views/CPUTimelineOverviewGraph.css:
+        (.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.total-usage):
+        (.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.main-thread-usage):
+        (.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.worker-thread-usage):
+        Updated colors.
+
+        * UserInterface/Views/CPUUsageCombinedView.css: Renamed from Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.css.
+        (.cpu-usage-combined-view > .details > .legend-container):
+        (.cpu-usage-combined-view > .details > .legend-container > .row):
+        (.cpu-usage-combined-view > .details > .legend-container > .row + .row):
+        (.cpu-usage-combined-view > .details > .legend-container > .row > .swatch):
+        * UserInterface/Views/CPUUsageCombinedView.js: Renamed from Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.js.
+        (WI.CPUUsageCombinedView.appendLegendRow):
+        (WI.CPUUsageCombinedView):
+        (WI.CPUUsageCombinedView.prototype.get graphElement):
+        (WI.CPUUsageCombinedView.prototype.get chart):
+        (WI.CPUUsageCombinedView.prototype.get rangeChart):
+        (WI.CPUUsageCombinedView.prototype.clear):
+        (WI.CPUUsageCombinedView.prototype.updateChart):
+        (WI.CPUUsageCombinedView.prototype.updateMainThreadIndicator):
+        (WI.CPUUsageCombinedView.prototype.clearLegend):
+        (WI.CPUUsageCombinedView.prototype.updateLegend):
+        (WI.CPUUsageCombinedView.prototype._updateDetails):
+        * UserInterface/Views/CPUUsageIndicatorView.css: Removed.
+        * UserInterface/Views/CPUUsageIndicatorView.js: Removed.
+        Combined the Indicator and StackedAreaChart into a single view
+        that share a left details section.
+
+        * UserInterface/Views/CPUUsageView.js:
+        (WI.CPUUsageView):
+        (WI.CPUUsageView.prototype.get graphElement):
+        (WI.CPUUsageView.prototype.clear):
+        (WI.CPUUsageView.prototype.updateChart):
+        (WI.CPUUsageView.prototype.clearLegend):
+        (WI.CPUUsageView.prototype.updateLegend):
+        (WI.CPUUsageView.prototype._updateDetails):
+        Include a legend in the left details section.
+
+        * UserInterface/Views/AreaChart.js:
+        (WI.AreaChart):
+        (WI.AreaChart.prototype.addPointMarker):
+        (WI.AreaChart.prototype.clearPointMarkers):
+        (WI.AreaChart.prototype.clear):
+        (WI.AreaChart.prototype.layout):
+        * UserInterface/Views/StackedAreaChart.js:
+        (WI.StackedAreaChart):
+        (WI.StackedAreaChart.prototype.addPointMarker):
+        (WI.StackedAreaChart.prototype.clearPointMarkers):
+        (WI.StackedAreaChart.prototype.clear):
+        (WI.StackedAreaChart.prototype.layout):
+        Add point markers for the area charts.
+
+        * UserInterface/Views/CPUTimelineView.css:
+        * UserInterface/Views/CPUTimelineView.js:
+        (WI.CPUTimelineView):
+        (WI.CPUTimelineView.prototype.get cpuUsageViewHeight):
+        (WI.CPUTimelineView.prototype.clear):
+        (WI.CPUTimelineView.prototype.initialLayout.appendLegendRow):
+        (WI.CPUTimelineView.prototype.initialLayout):
+        (WI.CPUTimelineView.prototype.layout):
+        (WI.CPUTimelineView.prototype._graphPositionForMouseEvent):
+        (WI.CPUTimelineView.prototype._handleMouseClick):
+        (WI.CPUTimelineView.prototype._handleGraphMouseMove):
+        (WI.CPUTimelineView.prototype._showGraphOverlayNearTo):
+        (WI.CPUTimelineView.prototype._updateGraphOverlay):
+        (WI.CPUTimelineView.prototype._showGraphOverlay.xScale):
+        (WI.CPUTimelineView.prototype._showGraphOverlay.yScale):
+        (WI.CPUTimelineView.prototype._showGraphOverlay.addOverlayPoint):
+        (WI.CPUTimelineView.prototype._showGraphOverlay):
+        (WI.CPUTimelineView.prototype._clearOverlayMarkers.clearGraphOverlayElement):
+        (WI.CPUTimelineView.prototype._clearOverlayMarkers):
+        (WI.CPUTimelineView.prototype._hideGraphOverlay):
+        Include graph overlay markers.
+
 2019-03-11  Devin Rousso  <drousso@apple.com>
 
         Web Inspector: eliminate manual syncing of numeric constants used by JavaScript and CSS
index 365fade..374617b 100644 (file)
@@ -616,6 +616,7 @@ localizedStrings["MIME Type"] = "MIME Type";
 localizedStrings["MIME Type:"] = "MIME Type:";
 localizedStrings["MSE Logging:"] = "MSE Logging:";
 localizedStrings["Main Thread"] = "Main Thread";
+localizedStrings["Main: %s"] = "Main: %s";
 localizedStrings["Manifest URL"] = "Manifest URL";
 localizedStrings["Mass"] = "Mass";
 localizedStrings["Matching"] = "Matching";
@@ -710,6 +711,7 @@ localizedStrings["Originator"] = "Originator";
 localizedStrings["Other"] = "Other";
 localizedStrings["Other Issue"] = "Other Issue";
 localizedStrings["Other Threads"] = "Other Threads";
+localizedStrings["Other: %s"] = "Other: %s";
 localizedStrings["Other\u2026"] = "Other\u2026";
 localizedStrings["Outgoing message"] = "Outgoing message";
 localizedStrings["Output: "] = "Output: ";
@@ -1056,6 +1058,7 @@ localizedStrings["Total"] = "Total";
 localizedStrings["Total Time"] = "Total Time";
 localizedStrings["Total memory size at the end of the selected time range"] = "Total memory size at the end of the selected time range";
 localizedStrings["Total time"] = "Total time";
+localizedStrings["Total: %s"] = "Total: %s";
 localizedStrings["Totals:"] = "Totals:";
 localizedStrings["Trace"] = "Trace";
 localizedStrings["Trace: %s"] = "Trace: %s";
@@ -1082,6 +1085,7 @@ localizedStrings["Unknown node"] = "Unknown node";
 localizedStrings["Unsupported property name"] = "Unsupported property name";
 localizedStrings["Unsupported property value"] = "Unsupported property value";
 localizedStrings["Untitled"] = "Untitled";
+localizedStrings["Usage: %s"] = "Usage: %s";
 localizedStrings["Use Default Appearance"] = "Use Default Appearance";
 localizedStrings["Use Default Media Styles"] = "Use Default Media Styles";
 localizedStrings["Use Mock Capture Devices"] = "Use Mock Capture Devices";
@@ -1121,7 +1125,9 @@ localizedStrings["Width"] = "Width";
 localizedStrings["With Object Properties"] = "With Object Properties";
 localizedStrings["Worker"] = "Worker";
 localizedStrings["Worker Thread"] = "Worker Thread";
+localizedStrings["Worker Threads"] = "Worker Threads";
 localizedStrings["Worker \u2014 %s"] = "Worker \u2014 %s";
+localizedStrings["Worker: %s"] = "Worker: %s";
 localizedStrings["Working Copy"] = "Working Copy";
 localizedStrings["Wrap lines to editor width"] = "Wrap lines to editor width";
 localizedStrings["XHR"] = "XHR";
index b7ab243..2656e67 100644 (file)
@@ -45,8 +45,7 @@
     <link rel="stylesheet" href="Views/ButtonToolbarItem.css">
     <link rel="stylesheet" href="Views/CPUTimelineOverviewGraph.css">
     <link rel="stylesheet" href="Views/CPUTimelineView.css">
-    <link rel="stylesheet" href="Views/CPUUsageIndicatorView.css">
-    <link rel="stylesheet" href="Views/CPUUsageStackedView.css">
+    <link rel="stylesheet" href="Views/CPUUsageCombinedView.css">
     <link rel="stylesheet" href="Views/CPUUsageView.css">
     <link rel="stylesheet" href="Views/CallFrameIcons.css">
     <link rel="stylesheet" href="Views/CallFrameTreeElement.css">
     <script src="Views/ButtonToolbarItem.js"></script>
     <script src="Views/CPUTimelineOverviewGraph.js"></script>
     <script src="Views/CPUTimelineView.js"></script>
-    <script src="Views/CPUUsageIndicatorView.js"></script>
-    <script src="Views/CPUUsageStackedView.js"></script>
+    <script src="Views/CPUUsageCombinedView.js"></script>
     <script src="Views/CPUUsageView.js"></script>
     <script src="Views/CSSStyleSheetTreeElement.js"></script>
     <script src="Views/CallFrameTreeElement.js"></script>
index c8d4569..c590143 100644 (file)
@@ -26,7 +26,8 @@
 // AreaChart creates a single filled area chart.
 //
 // Initialize the chart with a size. You can then include a new point
-// in the area chart by providing an (x, y) point via `addPoint`.
+// in the area chart by providing an (x, y) point via `addPoint`. You
+// can add point markers (<circle>) with an (x, y) as well.
 //
 // SVG:
 //
@@ -50,8 +51,10 @@ WI.AreaChart = class AreaChart extends WI.View
         this._chartElement.setAttribute("preserveAspectRatio", "none");
 
         this._pathElement = this._chartElement.appendChild(createSVGElement("path"));
+        this._circleElements = [];
 
         this._points = [];
+        this._markers = [];
         this._size = null;
     }
 
@@ -79,11 +82,27 @@ WI.AreaChart = class AreaChart extends WI.View
         this._points.push({x, y});
     }
 
-    clear()
+    clearPoints()
     {
         this._points = [];
     }
 
+    addPointMarker(x, y)
+    {
+        this._markers.push({x, y});
+    }
+
+    clearPointMarkers()
+    {
+        this._markers = [];
+    }
+
+    clear()
+    {
+        this.clearPoints();
+        this.clearPointMarkers();
+    }
+
     // Protected
 
     layout()
@@ -107,5 +126,20 @@ WI.AreaChart = class AreaChart extends WI.View
 
         let pathString = pathComponents.join(" ");
         this._pathElement.setAttribute("d", pathString);
+
+        if (this._circleElements.length) {
+            for (let circle of this._circleElements)
+                circle.remove();
+            this._circleElements = [];
+        }
+
+        if (this._markers.length) {
+            for (let {x, y} of this._markers) {
+                let circle = this._chartElement.appendChild(createSVGElement("circle"));
+                this._circleElements.push(circle);
+                circle.setAttribute("cx", x);
+                circle.setAttribute("cy", y);
+            }
+        }
     }
 };
index 688c76c..2bc36b0 100644 (file)
@@ -61,17 +61,19 @@ body[dir=rtl] .timeline-overview-graph.cpu > .stacked-column-chart {
     transform: scaleX(-1);
 }
 
-.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect {
-    stroke: var(--cpu-stroke-color);
-    fill: var(--cpu-fill-color);
+.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.total-usage {
+    fill: var(--cpu-other-thread-fill-color);
+    stroke: var(--cpu-other-thread-stroke-color);
 }
 
 .timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.main-thread-usage {
     fill: var(--cpu-main-thread-fill-color);
+    stroke: var(--cpu-main-thread-stroke-color);
 }
 
 .timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.worker-thread-usage {
     fill: var(--cpu-worker-thread-fill-color);
+    stroke: var(--cpu-worker-thread-stroke-color);
 }
 
 .timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.selected {
index b842b3d..816ee54 100644 (file)
@@ -126,47 +126,42 @@ body[dir=rtl] .timeline-view.cpu > .content > .overview > .divider {
     color: var(--text-color-secondary);
 }
 
-.timeline-view.cpu .legend {
+.timeline-view.cpu > .content > .overview .legend {
     -webkit-padding-start: 20px;
     text-align: start;
 }
 
-.timeline-view.cpu .legend .row {
+.timeline-view.cpu > .content > .overview .legend .row {
     display: flex;
 }
 
-.timeline-view.cpu .legend .row + .row {
+.timeline-view.cpu > .content > .overview .legend .row + .row {
     margin-top: 4px;
 }
 
-.timeline-view.cpu .legend .swatch {
+.timeline-view.cpu > .content > .overview .legend .swatch {
     width: 1em;
     height: 1em;
     margin-top: 1px;
     -webkit-margin-end: 8px;
 }
 
-.timeline-view.cpu .legend > .row > .swatch.sample-type-idle {
-    border: 1px solid var(--cpu-idle-stroke-color);
-    background-color: var(--cpu-idle-fill-color);
-}
-
-.timeline-view.cpu .legend > .row > .swatch.sample-type-script {
+.timeline-view.cpu > .content > .overview .legend > .row > .swatch.sample-type-script {
     border: 1px solid var(--cpu-script-stroke-color);
     background-color: var(--cpu-script-fill-color);
 }
 
-.timeline-view.cpu .legend > .row > .swatch.sample-type-style {
+.timeline-view.cpu > .content > .overview .legend > .row > .swatch.sample-type-style {
     border: 1px solid var(--cpu-style-stroke-color);
     background-color: var(--cpu-style-fill-color);
 }
 
-.timeline-view.cpu .legend > .row > .swatch.sample-type-layout {
+.timeline-view.cpu > .content > .overview .legend > .row > .swatch.sample-type-layout {
     border: 1px solid var(--cpu-layout-stroke-color);
     background-color: var(--cpu-layout-fill-color);
 }
 
-.timeline-view.cpu .legend > .row > .swatch.sample-type-paint {
+.timeline-view.cpu > .content > .overview .legend > .row > .swatch.sample-type-paint {
     border: 1px solid var(--cpu-paint-stroke-color);
     background-color: var(--cpu-paint-fill-color);
 }
@@ -197,22 +192,20 @@ body[dir=rtl] .timeline-view.cpu > .content > .overview > .divider {
 }
 
 .timeline-view.cpu :matches(.area-chart, .stacked-area-chart) svg > path {
-    stroke: var(--cpu-stroke-color);
-    fill: var(--cpu-fill-color);
+    fill: var(--cpu-other-thread-fill-color);
+    stroke: var(--cpu-other-thread-stroke-color);
 }
 
 .timeline-view.cpu .main-thread svg > path,
 .timeline-view.cpu svg > path.main-thread-usage {
     fill: var(--cpu-main-thread-fill-color);
+    stroke: var(--cpu-main-thread-stroke-color);
 }
 
 .timeline-view.cpu .worker-thread svg > path,
 .timeline-view.cpu svg > path.worker-thread-usage {
     fill: var(--cpu-worker-thread-fill-color);
-}
-
-.timeline-view.cpu .cpu-usage-view.empty {
-    display: none;
+    stroke: var(--cpu-worker-thread-stroke-color);
 }
 
 .timeline-view.cpu :matches(.area-chart, .stacked-area-chart) .markers {
@@ -247,28 +240,19 @@ body[dir=rtl] .timeline-view.cpu :matches(.area-chart, .stacked-area-chart) .mar
     background-color: var(--background-color-content);
 }
 
-.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart rect {
-    stroke-opacity: 0.25;
+.timeline-view.cpu :matches(.area-chart, .stacked-area-chart) circle {
+    r: 3;
+    fill: var(--cpu-overlay-color);
 }
 
-.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-script {
-    stroke: var(--cpu-script-stroke-color);
-    fill: var(--cpu-script-fill-color);
-}
-
-.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-style {
-    stroke: var(--cpu-style-stroke-color);
-    fill: var(--cpu-style-fill-color);
-}
-
-.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-layout {
-    stroke: var(--cpu-layout-stroke-color);
-    fill: var(--cpu-layout-fill-color);
+.timeline-view.cpu .timeline-ruler > .markers > .marker.timestamp {
+    color: var(--cpu-overlay-color);
+    opacity: 0.8;
+    pointer-events: none;
 }
 
-.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-paint {
-    stroke: var(--cpu-paint-stroke-color);
-    fill: var(--cpu-paint-fill-color);
+.timeline-view.cpu .timeline-ruler > .markers > .marker.timestamp::after {
+    display: none;
 }
 
 .timeline-view.cpu .gauge-chart .low {
index 5aceff3..59398f9 100644 (file)
@@ -35,9 +35,17 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
 
         this.element.classList.add("cpu");
 
-        this._statisticsData = null;
         this._sectionLimit = CPUTimelineView.defaultSectionLimit;
 
+        this._statisticsData = null;
+        this._secondsPerPixelInLayout = undefined;
+        this._visibleRecordsInLayout = [];
+        this._discontinuitiesInLayout = [];
+
+        this._stickingOverlay = false;
+        this._overlayRecord = null;
+        this._overlayTime = NaN;
+
         timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._cpuTimelineRecordAdded, this);
     }
 
@@ -58,7 +66,7 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
         console.error("Unknown sample type", type);
     }
 
-    static get cpuUsageViewHeight() { return 150; }
+    static get cpuUsageViewHeight() { return 135; }
     static get threadCPUUsageViewHeight() { return 65; }
     static get indicatorViewHeight() { return 15; }
 
@@ -124,10 +132,15 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
 
         this._removeWorkerThreadViews();
 
-        this._mainThreadWorkIndicatorView.clear();
+        this._sectionLimit = CPUTimelineView.defaultSectionLimit;
 
         this._statisticsData = null;
-        this._sectionLimit = CPUTimelineView.defaultSectionLimit;
+        this._secondsPerPixelInLayout = undefined;
+        this._visibleRecordsInLayout = [];
+        this._discontinuitiesInLayout = [];
+
+        this._stickingOverlay = false;
+        this._hideGraphOverlay();
     }
 
     // Protected
@@ -141,7 +154,7 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
 
     initialLayout()
     {
-        this.element.style.setProperty("--cpu-usage-stacked-view-height", CPUTimelineView.cpuUsageViewHeight + "px");
+        this.element.style.setProperty("--cpu-usage-combined-view-height", CPUTimelineView.cpuUsageViewHeight + "px");
         this.element.style.setProperty("--cpu-usage-view-height", CPUTimelineView.threadCPUUsageViewHeight + "px");
         this.element.style.setProperty("--cpu-usage-indicator-view-height", CPUTimelineView.indicatorViewHeight + "px");
 
@@ -174,7 +187,6 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
             swatchElement.classList.add("swatch", sampleType);
 
             let valueContainer = rowElement.appendChild(document.createElement("div"));
-            valueContainer.classList.add("value");
 
             let labelElement = valueContainer.appendChild(document.createElement("div"));
             labelElement.classList.add("label");
@@ -287,15 +299,11 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
         detailsSubtitleElement.classList.add("subtitle");
         detailsSubtitleElement.textContent = WI.UIString("CPU Usage");
 
-        this._cpuUsageView = new WI.CPUUsageStackedView(WI.UIString("Total"));
+        this._cpuUsageView = new WI.CPUUsageCombinedView(WI.UIString("Total"));
         this.addSubview(this._cpuUsageView);
         detailsContainerElement.appendChild(this._cpuUsageView.element);
 
-        this._mainThreadWorkIndicatorView = new WI.CPUUsageIndicatorView;
-        this.addSubview(this._mainThreadWorkIndicatorView);
-        detailsContainerElement.appendChild(this._mainThreadWorkIndicatorView.element);
-
-        this._mainThreadWorkIndicatorView.chart.element.addEventListener("click", this._handleIndicatorClick.bind(this));
+        this._cpuUsageView.rangeChart.element.addEventListener("click", this._handleIndicatorClick.bind(this));
 
         this._threadsDetailsElement = detailsContainerElement.appendChild(document.createElement("details"));
         this._threadsDetailsElement.open = WI.settings.cpuTimelineThreadDetailsExpanded.value;
@@ -315,12 +323,10 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
         this._threadsDetailsElement.appendChild(this._mainThreadUsageView.element);
 
         this._webkitThreadUsageView = new WI.CPUUsageView(WI.UIString("WebKit Threads"));
-        this._webkitThreadUsageView.element.classList.add("non-main-thread");
         this.addSubview(this._webkitThreadUsageView);
         this._threadsDetailsElement.appendChild(this._webkitThreadUsageView.element);
 
         this._unknownThreadUsageView = new WI.CPUUsageView(WI.UIString("Other Threads"));
-        this._unknownThreadUsageView.element.classList.add("non-main-thread");
         this.addSubview(this._unknownThreadUsageView);
         this._threadsDetailsElement.appendChild(this._unknownThreadUsageView.element);
 
@@ -398,7 +404,11 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
 
         this._clearSources();
 
+        this.element.addEventListener("click", this._handleGraphClick.bind(this));
         this.element.addEventListener("mousemove", this._handleGraphMouseMove.bind(this));
+
+        this._overlayMarker = new WI.TimelineMarker(-1, WI.TimelineMarker.Type.TimeStamp);
+        this._timelineRuler.addMarker(this._overlayMarker);
     }
 
     layout()
@@ -434,6 +444,10 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
             return;
         }
 
+        this._secondsPerPixelInLayout = secondsPerPixel;
+        this._visibleRecordsInLayout = visibleRecords;
+        this._discontinuitiesInLayout = discontinuities.slice();
+
         this._statisticsData = this._computeStatisticsData(graphStartTime, visibleEndTime);
         this._layoutBreakdownChart();
         this._layoutStatisticsAndSources();
@@ -610,6 +624,7 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
         // Layout all graphs to the same time scale. The maximum value is
         // the maximum total CPU usage across all threads.
         let layoutMax = max;
+        this._layoutMax = max;
 
         function layoutView(view, property, graphHeight, {dataPoints, min, max, average}) {
             if (min === Infinity)
@@ -684,6 +699,7 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
                 let displayName = worker ? worker.displayName : WI.UIString("Worker Thread");
                 let workerView = new WI.CPUUsageView(displayName);
                 workerView.element.classList.add("worker-thread");
+                workerView.__workerId = workerId;
                 this.addSubview(workerView);
                 this._threadsDetailsElement.insertBefore(workerView.element, this._webkitThreadUsageView.element);
                 this._workerViews.push(workerView);
@@ -698,9 +714,11 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
 
         let graphWidth = (graphEndTime - graphStartTime) / secondsPerPixel;
         let size = new WI.Size(graphWidth, CPUTimelineView.indicatorViewHeight);
-        this._mainThreadWorkIndicatorView.updateChart(this._statisticsData.samples, size, visibleEndTime, xScaleIndicatorRange);
+        this._cpuUsageView.updateMainThreadIndicator(this._statisticsData.samples, size, visibleEndTime, xScaleIndicatorRange);
 
         this._layoutEnergyChart(average, visibleDuration);
+
+        this._updateGraphOverlay();
     }
 
     // Private
@@ -1488,11 +1506,11 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
         if (!chartElement)
             return NaN;
 
-        let chartRect = chartElement.getBoundingClientRect();
-        let position = event.pageX - chartRect.left;
+        let rect = chartElement.getBoundingClientRect();
+        let position = event.pageX - rect.left;
 
         if (WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL)
-            return chartRect.width - position;
+            return rect.width - position;
         return position;
     }
 
@@ -1591,10 +1609,23 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
         this.dispatchEventToListeners(WI.TimelineView.Event.RecordWasSelected, {record});
     }
 
+    _handleGraphClick(event)
+    {
+        let mousePosition = this._graphPositionForMouseEvent(event);
+        if (isNaN(mousePosition))
+            return;
+
+        this._stickingOverlay = !this._stickingOverlay;
+
+        if (!this._stickingOverlay)
+            this._handleGraphMouseMove(event);
+    }
+
     _handleGraphMouseMove(event)
     {
         let mousePosition = this._graphPositionForMouseEvent(event);
         if (isNaN(mousePosition)) {
+            this._hideGraphOverlay();
             this.dispatchEventToListeners(WI.TimelineView.Event.ScannerHide);
             return;
         }
@@ -1602,8 +1633,162 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
         let secondsPerPixel = this._timelineRuler.secondsPerPixel;
         let time = this.startTime + (mousePosition * secondsPerPixel);
 
+        if (!this._stickingOverlay)
+            this._showGraphOverlayNearTo(time);
+
         this.dispatchEventToListeners(WI.TimelineView.Event.ScannerShow, {time});
     }
+
+    _showGraphOverlayNearTo(time)
+    {
+        let nearestRecord = null;
+        let nearestDistance = Infinity;
+
+        // Find the nearest record to the time.
+        for (let record of this._visibleRecordsInLayout) {
+            let distance = Math.abs(time - record.timestamp);
+            if (distance < nearestDistance) {
+                nearestRecord = record;
+                nearestDistance = distance;
+            }
+        }
+
+        if (!nearestRecord) {
+            this._hideGraphOverlay();
+            return;
+        }
+
+        let bestTime = nearestRecord.timestamp;
+
+        // Snap to a discontinuity if closer.
+        for (let {startTime, endTime} of this._discontinuitiesInLayout) {
+            let distance = Math.abs(time - startTime);
+            if (distance < nearestDistance) {
+                nearestDistance = distance;
+                bestTime = startTime;
+            }
+            distance = Math.abs(time - endTime);
+            if (distance < nearestDistance) {
+                nearestDistance = distance;
+                bestTime = endTime;
+            }
+        }
+
+        // Snap to end time if closer.
+        let visibleEndTime = Math.min(this.endTime, this.currentTime);
+        let distance = Math.abs(time - visibleEndTime);
+        if (distance < nearestDistance) {
+            nearestDistance = distance;
+            bestTime = visibleEndTime;
+        }
+
+        let graphStartTime = this.startTime;
+        let adjustedTime = Number.constrain(bestTime, graphStartTime, visibleEndTime);
+        this._showGraphOverlay(nearestRecord, adjustedTime);
+    }
+
+    _updateGraphOverlay()
+    {
+        if (!this._overlayRecord)
+            return;
+
+        this._showGraphOverlay(this._overlayRecord, this._overlayTime, true);
+    }
+
+    _showGraphOverlay(record, time, force)
+    {
+        if (!force && record === this._overlayRecord && time === this._overlayTime)
+            return;
+
+        this._overlayRecord = record;
+        this._overlayTime = time;
+
+        let layoutMax = this._layoutMax;
+        let secondsPerPixel = this._secondsPerPixelInLayout;
+        let graphMax = layoutMax * 1.05;
+        let graphStartTime = this.startTime;
+
+        this._overlayMarker.time = time + (secondsPerPixel / 2);
+
+        function xScale(time) {
+            return (time - graphStartTime) / secondsPerPixel;
+        }
+
+        let x = xScale(time);
+
+        let {mainThreadUsage, workerThreadUsage, webkitThreadUsage, unknownThreadUsage, workersData} = record;
+
+        function addOverlayPoint(view, graphHeight, value) {
+            if (!value)
+                return;
+
+            function yScale(value) {
+                return graphHeight - ((value / graphMax) * graphHeight);
+            }
+
+            view.chart.addPointMarker(x, yScale(value));
+            view.chart.needsLayout();
+        }
+
+        this._clearOverlayMarkers();
+
+        this._cpuUsageView.updateLegend(record);
+        addOverlayPoint(this._cpuUsageView, CPUTimelineView.cpuUsageViewHeight, mainThreadUsage);
+        addOverlayPoint(this._cpuUsageView, CPUTimelineView.cpuUsageViewHeight, mainThreadUsage + workerThreadUsage);
+        addOverlayPoint(this._cpuUsageView, CPUTimelineView.cpuUsageViewHeight, mainThreadUsage + workerThreadUsage + webkitThreadUsage + unknownThreadUsage);
+
+        if (this._threadsDetailsElement.open) {
+            this._mainThreadUsageView.updateLegend(mainThreadUsage);
+            addOverlayPoint(this._mainThreadUsageView, CPUTimelineView.threadCPUUsageViewHeight, mainThreadUsage);
+
+            this._webkitThreadUsageView.updateLegend(webkitThreadUsage);
+            addOverlayPoint(this._webkitThreadUsageView, CPUTimelineView.threadCPUUsageViewHeight, webkitThreadUsage);
+
+            this._unknownThreadUsageView.updateLegend(unknownThreadUsage);
+            addOverlayPoint(this._unknownThreadUsageView, CPUTimelineView.threadCPUUsageViewHeight, unknownThreadUsage);
+
+            for (let workerView of this._workerViews)
+                workerView.updateLegend(NaN);
+
+            if (workersData) {
+                for (let {targetId, usage} of workersData) {
+                    let workerView = this._workerViews.find((x) => x.__workerId === targetId);
+                    if (workerView) {
+                        workerView.updateLegend(usage);
+                        addOverlayPoint(workerView, CPUTimelineView.threadCPUUsageViewHeight, usage);
+                    }
+                }
+            }
+        }
+    }
+
+    _clearOverlayMarkers()
+    {
+        function clearGraphOverlayElement(view) {
+            view.clearLegend();
+            view.chart.clearPointMarkers();
+            view.chart.needsLayout();
+        }
+
+        clearGraphOverlayElement(this._cpuUsageView);
+        clearGraphOverlayElement(this._mainThreadUsageView);
+        clearGraphOverlayElement(this._webkitThreadUsageView);
+        clearGraphOverlayElement(this._unknownThreadUsageView);
+
+        for (let workerView of this._workerViews)
+            clearGraphOverlayElement(workerView);
+    }
+
+    _hideGraphOverlay()
+    {
+        if (this._stickingOverlay)
+            return;
+
+        this._overlayRecord = null;
+        this._overlayTime = NaN;
+        this._overlayMarker.time = -1;
+        this._clearOverlayMarkers();
+    }
 };
 
 WI.CPUTimelineView.LayoutReason = {
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUUsageCombinedView.css b/Source/WebInspectorUI/UserInterface/Views/CPUUsageCombinedView.css
new file mode 100644 (file)
index 0000000..bfa424e
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+.cpu-usage-combined-view {
+    display: flex;
+    width: 100%;
+    height: calc(var(--cpu-usage-combined-view-height) + var(--cpu-usage-indicator-view-height) + 2px); /* +2 for borders */
+    border-bottom: 1px solid var(--border-color);
+}
+
+.cpu-usage-combined-view > .graph,
+.cpu-usage-combined-view > .graph > :matches(.stacked-area-chart, .range-chart),
+.cpu-usage-combined-view > .graph > :matches(.stacked-area-chart, .range-chart) > svg {
+    width: 100%;
+    height: 100%;
+}
+
+.cpu-usage-combined-view > .graph > .stacked-area-chart {
+    height: calc(var(--cpu-usage-combined-view-height));
+}
+
+.cpu-usage-combined-view > .graph > .range-chart {
+    position: relative;
+    z-index: calc(var(--timeline-marker-z-index) + 1);
+    height: calc(var(--cpu-usage-indicator-view-height) + 2px); /* +2 for borders */
+    background-color: var(--background-color-content);
+    border-top: 1px solid var(--border-color);
+    border-bottom: 1px solid var(--border-color);
+}
+
+.cpu-usage-combined-view > .details {
+    flex-shrink: 0;
+    width: 150px;
+    padding-top: 10px;
+    -webkit-padding-start: 15px;
+    font-size: 12px;
+    color: var(--text-color-secondary);
+    overflow: hidden;
+    text-overflow: ellipsis;
+
+    --cpu-usage-combined-view-details-border-end: 1px solid var(--border-color);
+}
+
+body[dir=ltr] .cpu-usage-combined-view > .details {
+    border-right: var(--cpu-usage-combined-view-details-border-end);
+}
+
+body[dir=rtl] .cpu-usage-combined-view > .details {
+    border-left: var(--cpu-usage-combined-view-details-border-end);
+}
+
+.cpu-usage-combined-view > :matches(.details, .legend) > .name {
+    color: var(--text-color);
+    white-space: nowrap;
+}
+
+.cpu-usage-combined-view > .graph {
+    position: relative;
+}
+
+body[dir=rtl] .cpu-usage-combined-view > .graph {
+    transform: scaleX(-1);
+}
+
+.cpu-usage-combined-view > .details > .legend-container {
+    font-size: 11px;
+}
+
+.cpu-usage-combined-view > .details > .legend-container > .row {
+    display: flex;
+}
+
+.cpu-usage-combined-view > .details > .legend-container > .row + .row {
+    margin-top: 4px;
+}
+
+.cpu-usage-combined-view > .details > .legend-container > .row > .swatch {
+    width: 1em;
+    height: 1em;
+    margin-top: 1px;
+    -webkit-margin-end: 4px;
+}
+
+.cpu-usage-combined-view > .details > .legend-container .swatch.total {
+    background-color: none;
+    border: none;
+}
+
+.cpu-usage-combined-view > .details > .legend-container .swatch.other-threads {
+    background-color: var(--cpu-other-thread-fill-color);
+    border: 1px solid var(--cpu-other-thread-stroke-color);
+}
+
+.cpu-usage-combined-view > .details > .legend-container .swatch.main-thread {
+    background-color: var(--cpu-main-thread-fill-color);
+    border: 1px solid var(--cpu-main-thread-stroke-color);
+}
+
+.cpu-usage-combined-view > .details > .legend-container .swatch.worker-threads {
+    background-color: var(--cpu-worker-thread-fill-color);
+    border: 1px solid var(--cpu-worker-thread-stroke-color);
+}
+
+.cpu-usage-combined-view > .graph > .range-chart rect {
+    stroke-opacity: 0.25;
+}
+
+.cpu-usage-combined-view > .graph > .range-chart .sample-type-script {
+    stroke: var(--cpu-script-stroke-color);
+    fill: var(--cpu-script-fill-color);
+}
+
+.cpu-usage-combined-view > .graph > .range-chart .sample-type-style {
+    stroke: var(--cpu-style-stroke-color);
+    fill: var(--cpu-style-fill-color);
+}
+
+.cpu-usage-combined-view > .graph > .range-chart .sample-type-layout {
+    stroke: var(--cpu-layout-stroke-color);
+    fill: var(--cpu-layout-fill-color);
+}
+
+.cpu-usage-combined-view > .graph > .range-chart .sample-type-paint {
+    stroke: var(--cpu-paint-stroke-color);
+    fill: var(--cpu-paint-fill-color);
+}
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WI.CPUUsageStackedView = class CPUUsageStackedView extends WI.View
+WI.CPUUsageCombinedView = class CPUUsageCombinedView extends WI.View
 {
     constructor(displayName)
     {
         super();
 
-        this.element.classList.add("cpu-usage-stacked-view");
+        this.element.classList.add("cpu-usage-combined-view");
 
         this._detailsElement = this.element.appendChild(document.createElement("div"));
         this._detailsElement.classList.add("details");
@@ -43,31 +43,66 @@ WI.CPUUsageStackedView = class CPUUsageStackedView extends WI.View
         this._detailsElement.appendChild(document.createElement("br"));
         this._detailsMaxElement = this._detailsElement.appendChild(document.createElement("span"));
         this._detailsElement.appendChild(document.createElement("br"));
-        this._detailsMinElement = this._detailsElement.appendChild(document.createElement("span"));
+        this._detailsElement.appendChild(document.createElement("br"));
         this._updateDetails(NaN, NaN);
 
         this._graphElement = this.element.appendChild(document.createElement("div"));
         this._graphElement.classList.add("graph");
 
+        // Combined thread usage area chart.
         this._chart = new WI.StackedAreaChart;
         this._chart.initializeSections(["main-thread-usage", "worker-thread-usage", "total-usage"]);
         this.addSubview(this._chart);
         this._graphElement.appendChild(this._chart.element);
+
+        // Main thread indicator strip.
+        this._rangeChart = new WI.RangeChart;
+        this.addSubview(this._rangeChart);
+        this._graphElement.appendChild(this._rangeChart.element);
+
+        function appendLegendRow(legendElement, className) {
+            let rowElement = legendElement.appendChild(document.createElement("div"));
+            rowElement.classList.add("row");
+
+            let swatchElement = rowElement.appendChild(document.createElement("div"));
+            swatchElement.classList.add("swatch", className);
+
+            let labelElement = rowElement.appendChild(document.createElement("div"));
+            labelElement.classList.add("label");
+
+            return labelElement;
+        }
+
+        this._legendElement = this._detailsElement.appendChild(document.createElement("div"));
+        this._legendElement.classList.add("legend-container");
+
+        this._legendMainThreadElement = appendLegendRow(this._legendElement, "main-thread");
+        this._legendWorkerThreadsElement = appendLegendRow(this._legendElement, "worker-threads");
+        this._legendOtherThreadsElement = appendLegendRow(this._legendElement, "other-threads");
+        this._legendTotalThreadsElement = appendLegendRow(this._legendElement, "total");
+
+        this.clearLegend();
     }
 
     // Public
 
+    get graphElement() { return this._graphElement; }
     get chart() { return this._chart; }
+    get rangeChart() { return this._rangeChart; }
 
     clear()
     {
         this._cachedAverageSize = undefined;
-        this._cachedMinSize = undefined;
         this._cachedMaxSize = undefined;
         this._updateDetails(NaN, NaN);
 
+        this.clearLegend();
+
         this._chart.clear();
         this._chart.needsLayout();
+
+        this._rangeChart.clear();
+        this._rangeChart.needsLayout();
     }
 
     updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale)
@@ -78,9 +113,9 @@ WI.CPUUsageStackedView = class CPUUsageStackedView extends WI.View
         console.assert(min <= max);
         console.assert(min <= average && average <= max);
 
-        this._updateDetails(min, max, average);
+        this._updateDetails(max, average);
 
-        this._chart.clear();
+        this._chart.clearPoints();
         this._chart.size = size;
         this._chart.needsLayout();
 
@@ -116,19 +151,98 @@ WI.CPUUsageStackedView = class CPUUsageStackedView extends WI.View
         this._chart.addPointSet(lastX, [lastY1, lastY2, lastY3]);
     }
 
+    updateMainThreadIndicator(samples, size, visibleEndTime, xScale)
+    {
+        console.assert(size instanceof WI.Size);
+
+        this._rangeChart.clear();
+        this._rangeChart.size = size;
+        this._rangeChart.needsLayout();
+
+        if (!samples.length)
+            return;
+
+        // Coalesce ranges of samples.
+        let ranges = [];
+        let currentRange = null;
+        let currentSampleType = undefined;
+        for (let i = 0; i < samples.length; ++i) {
+            // Back to idle, close any current chunk.
+            let type = samples[i];
+            if (!type) {
+                if (currentRange) {
+                    ranges.push(currentRange);
+                    currentRange = null;
+                    currentSampleType = undefined;
+                }
+                continue;
+            }
+
+            // Expand existing chunk.
+            if (type === currentSampleType) {
+                currentRange.endIndex = i;
+                continue;
+            }
+
+            // If type changed, close current chunk.
+            if (currentSampleType) {
+                ranges.push(currentRange);
+                currentRange = null;
+                currentSampleType = undefined;
+            }
+
+            // Start a new chunk.
+            console.assert(!currentRange);
+            console.assert(!currentSampleType);
+            currentRange = {type, startIndex: i, endIndex: i};
+            currentSampleType = type;
+        }
+
+        for (let {type, startIndex, endIndex} of ranges) {
+            let startX = xScale(startIndex);
+            let endX = xScale(endIndex + 1);
+            let width = endX - startX;
+            this._rangeChart.addRange(startX, width, type);
+        }
+    }
+
+    clearLegend()
+    {
+        this._legendMainThreadElement.textContent = WI.UIString("Main Thread");
+        this._legendWorkerThreadsElement.textContent = WI.UIString("Worker Threads");
+        this._legendOtherThreadsElement.textContent = WI.UIString("Other Threads");
+        this._legendTotalThreadsElement.textContent = "";
+    }
+
+    updateLegend(record)
+    {
+        if (!record) {
+            this.clearLegend();
+            return;
+        }
+
+        let {usage, mainThreadUsage, workerThreadUsage, webkitThreadUsage, unknownThreadUsage} = record;
+
+        this._legendMainThreadElement.textContent = WI.UIString("Main: %s").format(Number.percentageString(mainThreadUsage / 100));
+        this._legendWorkerThreadsElement.textContent = WI.UIString("Worker: %s").format(Number.percentageString(workerThreadUsage / 100));
+        this._legendOtherThreadsElement.textContent = WI.UIString("Other: %s").format(Number.percentageString((webkitThreadUsage + unknownThreadUsage) / 100));
+        this._legendTotalThreadsElement.textContent = WI.UIString("Total: %s").format(Number.percentageString(usage / 100));
+    }
+
     // Private
 
-    _updateDetails(minSize, maxSize, averageSize)
+    _updateDetails(maxSize, averageSize)
     {
-        if (this._cachedMinSize === minSize && this._cachedMaxSize === maxSize && this._cachedAverageSize === averageSize)
+        if (this._cachedMaxSize === maxSize && this._cachedAverageSize === averageSize)
             return;
 
         this._cachedAverageSize = averageSize;
-        this._cachedMinSize = minSize;
         this._cachedMaxSize = maxSize;
 
         this._detailsAverageElement.textContent = WI.UIString("Average: %s").format(Number.isFinite(maxSize) ? Number.percentageString(averageSize / 100) : emDash);
         this._detailsMaxElement.textContent = WI.UIString("Highest: %s").format(Number.isFinite(maxSize) ? Number.percentageString(maxSize / 100) : emDash);
-        this._detailsMinElement.textContent = WI.UIString("Lowest: %s").format(Number.isFinite(minSize) ? Number.percentageString(minSize / 100) : emDash);
     }
 };
+
+WI.CPUUsageCombinedView._cachedMainThreadIndicatorFillColor = null;
+WI.CPUUsageCombinedView._cachedMainThreadIndicatorStrokeColor = null;
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUUsageIndicatorView.css b/Source/WebInspectorUI/UserInterface/Views/CPUUsageIndicatorView.css
deleted file mode 100644 (file)
index bdd904b..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-.cpu-usage-indicator-view {
-    display: flex;
-    width: 100%;
-    height: calc(var(--cpu-usage-indicator-view-height) + 1px); /* 1 for border-bottom */
-    border-bottom: 1px solid var(--border-color);
-}
-
-.cpu-usage-indicator-view > .details {
-    flex-shrink: 0;
-    width: 150px;
-    -webkit-padding-start: 15px;
-    
-    --cpu-usage-indicator-view-details-border-end: 1px solid var(--border-color);
-}
-
-body[dir=ltr] .cpu-usage-indicator-view > .details {
-    border-right: var(--cpu-usage-indicator-view-details-border-end);
-}
-
-body[dir=rtl] .cpu-usage-indicator-view > .details {
-    border-left: var(--cpu-usage-indicator-view-details-border-end);
-}
-
-body[dir=rtl] .cpu-usage-indicator-view > .graph {
-    transform: scaleX(-1);
-}
-
-.cpu-usage-indicator-view > .graph {
-    position: relative;
-    z-index: calc(var(--timeline-marker-z-index) + 1);
-    background-color: var(--background-color-content);
-}
-
-.cpu-usage-indicator-view > .graph,
-.cpu-usage-indicator-view > .graph > .range-chart,
-.cpu-usage-indicator-view > .graph > .range-chart > svg {
-    width: 100%;
-    height: 100%;
-}
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUUsageIndicatorView.js b/Source/WebInspectorUI/UserInterface/Views/CPUUsageIndicatorView.js
deleted file mode 100644 (file)
index 2705bd4..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-WI.CPUUsageIndicatorView = class CPUUsageIndicatorView extends WI.View
-{
-    constructor(delegate)
-    {
-        super();
-
-        this.element.classList.add("cpu-usage-indicator-view");
-
-        this._detailsElement = this.element.appendChild(document.createElement("div"));
-        this._detailsElement.classList.add("details");
-
-        this._graphElement = this.element.appendChild(document.createElement("div"));
-        this._graphElement.classList.add("graph");
-
-        this._chart = new WI.RangeChart;
-        this.addSubview(this._chart);
-        this._graphElement.appendChild(this._chart.element);
-    }
-
-    // Public
-
-    get chart() { return this._chart; }
-
-    clear()
-    {
-        this._chart.clear();
-        this._chart.needsLayout();
-    }
-
-    updateChart(samples, size, visibleEndTime, xScale)
-    {
-        console.assert(size instanceof WI.Size);
-
-        this._chart.clear();
-        this._chart.size = size;
-        this._chart.needsLayout();
-
-        if (!samples.length)
-            return;
-
-        // Coalesce ranges of samples.
-        let ranges = [];
-        let currentRange = null;
-        let currentSampleType = undefined;
-        for (let i = 0; i < samples.length; ++i) {
-            // Back to idle, close any current chunk.
-            let type = samples[i];
-            if (!type) {
-                if (currentRange) {
-                    ranges.push(currentRange);
-                    currentRange = null;
-                    currentSampleType = undefined;
-                }
-                continue;
-            }
-
-            // Expand existing chunk.
-            if (type === currentSampleType) {
-                currentRange.endIndex = i;
-                continue;
-            }
-
-            // If type changed, close current chunk.
-            if (currentSampleType) {
-                ranges.push(currentRange);
-                currentRange = null;
-                currentSampleType = undefined;
-            }
-
-            // Start a new chunk.
-            console.assert(!currentRange);
-            console.assert(!currentSampleType);
-            currentRange = {type, startIndex: i, endIndex: i};
-            currentSampleType = type;
-        }
-
-        for (let {type, startIndex, endIndex} of ranges) {
-            let startX = xScale(startIndex);
-            let endX = xScale(endIndex + 1);
-            let width = endX - startX;
-            this._chart.addRange(startX, width, type);
-        }
-    }
-};
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.css b/Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.css
deleted file mode 100644 (file)
index 80e126b..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-.cpu-usage-stacked-view {
-    display: flex;
-    width: 100%;
-    height: calc(var(--cpu-usage-stacked-view-height) + 1px); /* +1 for border-bottom */
-    border-bottom: 1px solid var(--border-color);
-}
-
-.cpu-usage-stacked-view > .details {
-    flex-shrink: 0;
-    width: 150px;
-    padding-top: 10px;
-    -webkit-padding-start: 15px;
-    font-family: -webkit-system-font, sans-serif;
-    font-size: 12px;
-    color: var(--text-color-secondary);
-    overflow: hidden;
-    text-overflow: ellipsis;
-
-    --cpu-usage-stacked-view-details-border-end: 1px solid var(--border-color);
-}
-
-body[dir=ltr] .cpu-usage-stacked-view > .details {
-    border-right: var(--cpu-usage-stacked-view-details-border-end);
-}
-
-body[dir=rtl] .cpu-usage-stacked-view > .details {
-    border-left: var(--cpu-usage-stacked-view-details-border-end);
-}
-
-.cpu-usage-stacked-view > .details > .name {
-    color: var(--text-color);
-    white-space: nowrap;
-}
-
-body[dir=rtl] .cpu-usage-stacked-view > .graph {
-    transform: scaleX(-1);
-}
-
-.cpu-usage-stacked-view > .graph {
-    position: relative;
-}
-
-.cpu-usage-stacked-view > .graph,
-.cpu-usage-stacked-view > .graph > .stacked-area-chart,
-.cpu-usage-stacked-view > .graph > .stacked-area-chart > svg {
-    width: 100%;
-    height: 100%;
-}
index 9622248..7986415 100644 (file)
@@ -43,8 +43,10 @@ WI.CPUUsageView = class CPUUsageView extends WI.View
 
         this._detailsAverageElement = this._detailsElement.appendChild(document.createElement("span"));
         this._detailsElement.appendChild(document.createElement("br"));
-        this._detailsMaxElement = this._detailsElement.appendChild(document.createElement("span"));
-        this._updateDetails(NaN, NaN);
+        this._detailsUsageElement = this._detailsElement.appendChild(document.createElement("span"));
+
+        this.clearLegend();
+        this._updateDetails(NaN);
 
         this._graphElement = this.element.appendChild(document.createElement("div"));
         this._graphElement.classList.add("graph");
@@ -56,15 +58,18 @@ WI.CPUUsageView = class CPUUsageView extends WI.View
 
     // Public
 
+    get graphElement() { return this._graphElement; }
     get chart() { return this._chart; }
 
     clear()
     {
         this._cachedAverageSize = undefined;
-        this._cachedMaxSize = undefined;
-        this._updateDetails(NaN, NaN);
+        this._updateDetails(NaN);
+
+        this.clearLegend();
 
         this._chart.clear();
+        this._chart.needsLayout();
     }
 
     updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale, property)
@@ -76,9 +81,9 @@ WI.CPUUsageView = class CPUUsageView extends WI.View
         console.assert(min <= average && average <= max);
         console.assert(property, "CPUUsageView needs a property of the dataPoints to graph");
 
-        this._updateDetails(min, max, average);
+        this._updateDetails(average);
 
-        this._chart.clear();
+        this._chart.clearPoints();
         this._chart.size = size;
         this._chart.needsLayout();
 
@@ -108,20 +113,30 @@ WI.CPUUsageView = class CPUUsageView extends WI.View
         this._chart.addPoint(lastX, lastY);
     }
 
+    clearLegend()
+    {
+        this._detailsUsageElement.hidden = true;
+        this._detailsUsageElement.textContent = emDash;
+    }
+
+    updateLegend(value)
+    {
+        let usage = Number.isFinite(value) ? Number.percentageString(value / 100) : emDash;
+
+        this._detailsUsageElement.hidden = false;
+        this._detailsUsageElement.textContent = WI.UIString("Usage: %s").format(usage);
+    }
+
     // Private
 
-    _updateDetails(minSize, maxSize, averageSize)
+    _updateDetails(averageSize)
     {
-        if (this._cachedMaxSize === maxSize && this._cachedAverageSize === averageSize)
+        if (this._cachedAverageSize === averageSize)
             return;
 
         this._cachedAverageSize = averageSize;
-        this._cachedMaxSize = maxSize;
 
         this._detailsAverageElement.hidden = !Number.isFinite(averageSize);
-        this._detailsMaxElement.hidden = !Number.isFinite(maxSize);
-
         this._detailsAverageElement.textContent = WI.UIString("Average: %s").format(Number.isFinite(averageSize) ? Number.percentageString(averageSize / 100) : emDash);
-        this._detailsMaxElement.textContent = WI.UIString("Highest: %s").format(Number.isFinite(maxSize) ? Number.percentageString(maxSize / 100) : emDash);
     }
 };
index 484cee6..a01e457 100644 (file)
@@ -29,7 +29,9 @@
 // To populate with data, first initialize the sections. The class names you
 // provide for the segments will allow you to style them. You can then include
 // a new set of (x, [y1, y2, y3]) points in the chart via `addPointSet`. The
-// order of `y` values must be in the same order as the sections.
+// order of `y` values must be in the same order as the sections. You can add
+// point markers (<circle>) with an (x, y) as well, note these are individual
+// instead of a set.
 //
 // SVG:
 //
@@ -56,8 +58,10 @@ WI.StackedAreaChart = class StackedAreaChart extends WI.View
         this._chartElement.setAttribute("preserveAspectRatio", "none");
 
         this._pathElements = [];
+        this._circleElements = [];
 
         this._points = [];
+        this._markers = [];
         this._size = null;
     }
 
@@ -101,11 +105,27 @@ WI.StackedAreaChart = class StackedAreaChart extends WI.View
         this._points.push({x, ys});
     }
 
-    clear()
+    clearPoints()
     {
         this._points = [];
     }
 
+    addPointMarker(x, y)
+    {
+        this._markers.push({x, y});
+    }
+
+    clearPointMarkers()
+    {
+        this._markers = [];
+    }
+
+    clear()
+    {
+        this.clearPoints();
+        this.clearPointMarkers();
+    }
+
     // Protected
 
     layout()
@@ -136,5 +156,21 @@ WI.StackedAreaChart = class StackedAreaChart extends WI.View
             let pathString = pathComponents[i].join(" ");
             this._pathElements[i].setAttribute("d", pathString);
         }
+
+        if (this._circleElements.length) {
+            for (let circle of this._circleElements)
+                circle.remove();
+            this._circleElements = [];
+        }
+
+        if (this._markers.length) {
+            for (let {x, y} of this._markers) {
+                let circle = this._chartElement.appendChild(createSVGElement("circle"));
+                this._circleElements.push(circle);
+                circle.setAttribute("cx", x);
+                circle.setAttribute("cy", y);
+                circle.setAttribute("r", 3);
+            }
+        }
     }
 };
index cc4bd09..72fa114 100644 (file)
     --memory-max-comparison-fill-color: hsl(220, 10%, 75%);
     --memory-max-comparison-stroke-color: hsl(220, 10%, 55%);
 
-    --cpu-stroke-color: hsl(118, 33%, 42%);
+    /* LegacyCPUTimeline */
     --cpu-fill-color: hsl(81, 80%, 50%);
+    --cpu-stroke-color: hsl(118, 33%, 42%);
+
+    --cpu-other-thread-fill-color: hsl(81, 80%, 50%);
+    --cpu-other-thread-stroke-color: hsl(81, 80%, 30%);
     --cpu-main-thread-fill-color: hsl(118, 43%, 55%);
-    --cpu-worker-thread-fill-color: hsl(45, 94.75%, 55%);
+    --cpu-main-thread-stroke-color: hsl(118, 33%, 42%);
+    --cpu-worker-thread-fill-color: hsl(59, 79%, 62%);
+    --cpu-worker-thread-stroke-color: hsl(59, 79%, 37%);
+    --cpu-overlay-color: var(--cpu-main-thread-stroke-color);
 
-    --cpu-idle-fill-color: hsl(220, 10%, 75%);
-    --cpu-idle-stroke-color: hsl(220, 10%, 55%);
     --cpu-script-fill-color: hsl(269, 65%, 75%);
     --cpu-script-stroke-color: hsl(269, 33%, 50%);
     --cpu-style-fill-color: hsl(22, 60%, 70%);
@@ -299,6 +304,8 @@ body.window-inactive * {
         --network-pseudo-header-color: hsl(312, 55%, 61%);
         --network-error-color: hsl(0, 54%, 55%);
 
+        --cpu-overlay-color: hsl(36, 98%, 50%);
+
         --cpu-low-color: hsl(110, 52%, 56%);
         --cpu-medium-color: hsl(46, 91%, 62%);
         --cpu-high-color: hsl(6, 79%, 57%);