Diff
Modified: trunk/Source/WebInspectorUI/ChangeLog (242299 => 242300)
--- trunk/Source/WebInspectorUI/ChangeLog 2019-03-02 01:05:30 UTC (rev 242299)
+++ trunk/Source/WebInspectorUI/ChangeLog 2019-03-02 01:18:13 UTC (rev 242300)
@@ -1,3 +1,76 @@
+2019-03-01 Joseph Pecoraro <pecor...@apple.com>
+
+ Web Inspector: CPU Usage - Energy Impact Section
+ https://bugs.webkit.org/show_bug.cgi?id=195151
+
+ Reviewed by Devin Rousso.
+
+ * Localizations/en.lproj/localizedStrings.js:
+ * UserInterface/Main.html:
+ New strings and resources.
+
+ * UserInterface/Views/CPUTimelineView.css:
+ (.timeline-view.cpu > .content .subtitle > .info):
+ (@media (prefers-color-scheme: dark)):
+ (.energy-info-popover-content):
+ (.timeline-view.cpu > .content > .overview > .divider):
+ (body[dir=ltr] .timeline-view.cpu > .content > .overview > .divider):
+ (body[dir=rtl] .timeline-view.cpu > .content > .overview > .divider):
+ (.timeline-view.cpu :matches(.area-chart, .stacked-area-chart) svg > path):
+ (.timeline-view.cpu .gauge-chart:not(.empty) > svg > path.low):
+ (.timeline-view.cpu .gauge-chart:not(.empty) > svg > path.medium):
+ (.timeline-view.cpu .gauge-chart:not(.empty) > svg > path.high):
+ (.timeline-view.cpu .gauge-chart:not(.empty) > svg > polygon.needle):
+ (.timeline-view.cpu .energy):
+ (.timeline-view.cpu .energy .energy-impact):
+ (.timeline-view.cpu .energy .energy-impact.low):
+ (.timeline-view.cpu .energy .energy-impact.medium):
+ (.timeline-view.cpu .energy .energy-impact.high):
+ (.timeline-view.cpu .energy .energy-impact-number):
+ Styling the chart and text for the different energy impact levels.
+
+ * UserInterface/Views/CPUTimelineView.js:
+ (WI.CPUTimelineView.prototype.get lowEnergyValue):
+ (WI.CPUTimelineView.prototype.get highEnergyValue):
+ (WI.CPUTimelineView.prototype.initialLayout):
+ (WI.CPUTimelineView.prototype.layout):
+ (WI.CPUTimelineView.prototype._layoutEnergyChart.mapWithBias):
+ (WI.CPUTimelineView.prototype._layoutEnergyChart.valuesForGauge):
+ (WI.CPUTimelineView.prototype._layoutEnergyChart):
+ (WI.CPUTimelineView.prototype._clearEnergyImpactText):
+ New gauge chart and associated popover.
+ We do a bit of biasing of the data for each of the sections
+ in the gauge chart. Each section biases toward the cap of the
+ section so that:
+ - we encourage lower power usage (sub 3%)
+ - the gauge needle quickly moves past the low value of a range
+
+ * UserInterface/Views/GaugeChart.css: Added.
+ (.gauge-chart):
+ (body[dir=rtl] .gauge-chart):
+ (.gauge-chart > svg > path,):
+ (.gauge-chart > svg > polygon.needle):
+ (.gauge-chart.empty > svg > polygon.needle):
+ (@media (prefers-color-scheme: dark)):
+ * UserInterface/Views/GaugeChart.js: Added.
+ (WI.GaugeChart.prototype.get size):
+ (WI.GaugeChart.prototype.get segments):
+ (WI.GaugeChart.prototype.get value):
+ (WI.GaugeChart.prototype.set value):
+ (WI.GaugeChart.prototype.clear):
+ (WI.GaugeChart.prototype.initialLayout):
+ (WI.GaugeChart.prototype.layout):
+ (WI.GaugeChart.prototype._validateSegments):
+ (WI.GaugeChart.prototype._createSegmentPathData):
+ GaugeChart with variable number of sections and a
+ current value needle. It has a bit of customization
+ when drawing the arc at the start of each segment.
+
+ * UserInterface/Views/Variables.css:
+ (:root):
+ (@media (prefers-color-scheme: dark)):
+ New CPU colors for the different energy impact levels.
+
2019-03-01 Nikita Vasilyev <nvasil...@apple.com>
Web Inspector: Data grid border colors don't match accent colors
Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (242299 => 242300)
--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js 2019-03-02 01:05:30 UTC (rev 242299)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js 2019-03-02 01:18:13 UTC (rev 242300)
@@ -141,6 +141,7 @@
localizedStrings["Auto Increment"] = "Auto Increment";
localizedStrings["Automatically continue after evaluating"] = "Automatically continue after evaluating";
localizedStrings["Available Style Sheets"] = "Available Style Sheets";
+localizedStrings["Average CPU: %s"] = "Average CPU: %s";
localizedStrings["Average Time"] = "Average Time";
localizedStrings["Average: %s"] = "Average: %s";
localizedStrings["Back (%s)"] = "Back (%s)";
@@ -338,6 +339,7 @@
localizedStrings["Download Web Archive"] = "Download Web Archive";
localizedStrings["Duplicate property"] = "Duplicate property";
localizedStrings["Duration"] = "Duration";
+localizedStrings["Duration: %s"] = "Duration: %s";
localizedStrings["Dynamically calculated for the parent element"] = "Dynamically calculated for the parent element";
localizedStrings["Dynamically calculated for the selected element"] = "Dynamically calculated for the selected element";
localizedStrings["Dynamically calculated for the selected element and did not match"] = "Dynamically calculated for the selected element and did not match";
@@ -393,6 +395,7 @@
localizedStrings["Enabled"] = "Enabled";
localizedStrings["Encoded"] = "Encoded";
localizedStrings["Encoding"] = "Encoding";
+localizedStrings["Energy Impact"] = "Energy Impact";
localizedStrings["Ensure aria-hidden=\u0022%s\u0022 is not used."] = "Ensure aria-hidden=\u0022%s\u0022 is not used.";
localizedStrings["Ensure that \u0022%s\u0022 is spelled correctly."] = "Ensure that \u0022%s\u0022 is spelled correctly.";
localizedStrings["Ensure that buttons have accessible labels for assistive technology."] = "Ensure that buttons have accessible labels for assistive technology.";
@@ -413,6 +416,7 @@
localizedStrings["Error: "] = "Error: ";
localizedStrings["Errors"] = "Errors";
localizedStrings["Errors:"] = "Errors:";
+localizedStrings["Estimated energy impact."] = "Estimated energy impact.";
localizedStrings["Eval Code"] = "Eval Code";
localizedStrings["Evaluate _javascript_"] = "Evaluate _javascript_";
localizedStrings["Event"] = "Event";
@@ -713,6 +717,7 @@
localizedStrings["Pause script execution (%s or %s)"] = "Pause script execution (%s or %s)";
/* The number of tests that passed expressed as a percentage, followed by a literal %. */
localizedStrings["Percentage (of audits)"] = "%s%%";
+localizedStrings["Periods of high CPU utilization will rapidly drain battery. Strive to keep idle pages under %s average CPU utilization."] = "Periods of high CPU utilization will rapidly drain battery. Strive to keep idle pages under %s average CPU utilization.";
localizedStrings["Ping"] = "Ping";
localizedStrings["Ping Frame"] = "Ping Frame";
localizedStrings["Pings"] = "Pings";
@@ -883,6 +888,7 @@
localizedStrings["Shadow Content"] = "Shadow Content";
localizedStrings["Shadow Content (%s)"] = "Shadow Content (%s)";
localizedStrings["Shared Focus"] = "Shared Focus";
+localizedStrings["Short"] = "Short";
localizedStrings["Shortest property path to %s"] = "Shortest property path to %s";
localizedStrings["Show %d More"] = "Show %d More";
localizedStrings["Show All"] = "Show All";
@@ -988,6 +994,7 @@
localizedStrings["The \u201C%s\u201D\ntable is empty."] = "The \u201C%s\u201D\ntable is empty.";
localizedStrings["The page's content has changed"] = "The page's content has changed";
localizedStrings["The resource was requested insecurely."] = "The resource was requested insecurely.";
+localizedStrings["There is an incurred energy penalty each time the page enters script. This commonly happens with timers, event handlers, and observers."] = "There is an incurred energy penalty each time the page enters script. This commonly happens with timers, event handlers, and observers.";
localizedStrings["These are all of the different test result levels."] = "These are all of the different test result levels.";
localizedStrings["These are all of the different types of data that can be returned with the test result."] = "These are all of the different types of data that can be returned with the test result.";
localizedStrings["These tests serve as a demonstration of the functionality and structure of audits."] = "These tests serve as a demonstration of the functionality and structure of audits.";
@@ -1020,6 +1027,7 @@
localizedStrings["Timer Removed"] = "Timer Removed";
localizedStrings["Timestamp \u2014 %s"] = "Timestamp \u2014 %s";
localizedStrings["Timing"] = "Timing";
+localizedStrings["To improve CPU utilization reduce or batch workloads when the page is not visible or during times when the page is not being interacted with."] = "To improve CPU utilization reduce or batch workloads when the page is not visible or during times when the page is not being interacted with.";
localizedStrings["Toggle Classes"] = "Toggle Classes";
localizedStrings["Toggle Visibility"] = "Toggle Visibility";
localizedStrings["Top Functions"] = "Top Functions";
@@ -1069,6 +1077,7 @@
localizedStrings["Version"] = "Version";
localizedStrings["Vertex"] = "Vertex";
localizedStrings["Vertex Shader"] = "Vertex Shader";
+localizedStrings["Very High"] = "Very High";
localizedStrings["View Image"] = "View Image";
localizedStrings["View Recording"] = "View Recording";
localizedStrings["View Shader"] = "View Shader";
Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (242299 => 242300)
--- trunk/Source/WebInspectorUI/UserInterface/Main.html 2019-03-02 01:05:30 UTC (rev 242299)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html 2019-03-02 01:18:13 UTC (rev 242300)
@@ -106,6 +106,7 @@
<link rel="stylesheet" href=""
<link rel="stylesheet" href=""
<link rel="stylesheet" href=""
+ <link rel="stylesheet" href=""
<link rel="stylesheet" href=""
<link rel="stylesheet" href=""
<link rel="stylesheet" href=""
@@ -672,6 +673,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css (242299 => 242300)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css 2019-03-02 01:05:30 UTC (rev 242299)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css 2019-03-02 01:18:13 UTC (rev 242300)
@@ -40,6 +40,23 @@
font-size: 14px;
}
+.timeline-view.cpu > .content .subtitle > .info {
+ display: inline-block;
+ width: 15px;
+ height: 15px;
+ -webkit-margin-start: 7px;
+ color: white;
+ font-size: 12px;
+ background-color: darkgray;
+ border-radius: 50%;
+}
+
+.energy-info-popover-content {
+ width: 275px;
+ padding: 0 5px;
+ -webkit-hyphens: auto;
+}
+
.timeline-view.cpu > .content > .details {
position: relative;
}
@@ -89,6 +106,20 @@
justify-content: center;
}
+.timeline-view.cpu > .content > .overview > .divider {
+ margin: 0 5px;
+
+ --cpu-timeline-view-overview-divider-border-end: 1px solid var(--border-color);
+}
+
+body[dir=ltr] .timeline-view.cpu > .content > .overview > .divider {
+ border-right: var(--cpu-timeline-view-overview-divider-border-end);
+}
+
+body[dir=rtl] .timeline-view.cpu > .content > .overview > .divider {
+ border-left: var(--cpu-timeline-view-overview-divider-border-end);
+}
+
.timeline-view.cpu > .content > .overview .samples,
.timeline-view.cpu > .content > .overview .legend .size {
margin: auto;
@@ -165,7 +196,7 @@
fill: var(--cpu-paint-fill-color);
}
-.timeline-view.cpu svg > path {
+.timeline-view.cpu :matches(.area-chart, .stacked-area-chart) svg > path {
stroke: var(--cpu-stroke-color);
fill: var(--cpu-fill-color);
}
@@ -239,3 +270,61 @@
stroke: var(--cpu-paint-stroke-color);
fill: var(--cpu-paint-fill-color);
}
+
+.timeline-view.cpu .gauge-chart .low {
+ --gauge-chart-path-fill-color: var(--cpu-low-color);
+ --gauge-chart-path-stroke-color: var(--cpu-low-color);
+}
+
+.timeline-view.cpu .gauge-chart .medium {
+ --gauge-chart-path-fill-color: var(--cpu-medium-color);
+ --gauge-chart-path-stroke-color: var(--cpu-medium-color);
+}
+
+.timeline-view.cpu .gauge-chart .high {
+ --gauge-chart-path-fill-color: var(--cpu-high-color);
+ --gauge-chart-path-stroke-color: var(--cpu-high-color);
+}
+
+.timeline-view.cpu .gauge-chart {
+ --gauge-chart-needle-fill-color: hsla(0, 0%, 36%, 0.85);
+ --gauge-chart-needle-stroke-color: hsla(0, 0%, 36%, 0.85);
+}
+
+.timeline-view.cpu .energy {
+ color: hsla(0, 0%, var(--foreground-lightness), 0.85);
+}
+
+.timeline-view.cpu .energy .energy-impact {
+ min-width: 140px;
+ margin-top: 15px;
+ font-size: 3em;
+ color: var(--text-color-secondary);
+}
+
+.timeline-view.cpu .energy .energy-impact.low {
+ color: var(--cpu-low-color);
+}
+
+.timeline-view.cpu .energy .energy-impact.medium {
+ color: var(--cpu-medium-color);
+}
+
+.timeline-view.cpu .energy .energy-impact.high {
+ color: var(--cpu-high-color);
+}
+
+.timeline-view.cpu .energy .energy-impact-number {
+ font-size: 14px;
+}
+
+@media (prefers-color-scheme: dark) {
+ .timeline-view.cpu > .content .subtitle > .info {
+ background-color: gray;
+ }
+
+ .timeline-view.cpu .gauge-chart:not(.empty) > svg > polygon.needle {
+ fill: hsla(0, 0%, var(--foreground-lightness), 0.85);
+ stroke: hsla(0, 0%, var(--foreground-lightness), 0.85);
+ }
+}
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js (242299 => 242300)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js 2019-03-02 01:05:30 UTC (rev 242299)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js 2019-03-02 01:18:13 UTC (rev 242300)
@@ -59,6 +59,10 @@
static get threadCPUUsageViewHeight() { return 65; }
static get indicatorViewHeight() { return 15; }
+ static get lowEnergyThreshold() { return 3; }
+ static get mediumEnergyThreshold() { return 50; }
+ static get highEnergyThreshold() { return 150; }
+
// Public
shown()
@@ -91,6 +95,10 @@
this._breakdownChart.needsLayout();
this._clearBreakdownLegend();
+ this._energyChart.clear();
+ this._energyChart.needsLayout();
+ this._clearEnergyImpactText();
+
function clearUsageView(view) {
view.clear();
@@ -178,6 +186,75 @@
this._breakdownLegendPaintElement = appendLegendRow(this._breakdownLegendElement, WI.CPUTimelineView.SampleType.Paint);
this._breakdownLegendStyleElement = appendLegendRow(this._breakdownLegendElement, WI.CPUTimelineView.SampleType.Style);
+ let dividerElement = overviewElement.appendChild(document.createElement("div"));
+ dividerElement.classList.add("divider");
+
+ let energyTooltip = WI.UIString("Estimated energy impact.")
+ let energyContainerElement = createChartContainer(overviewElement, WI.UIString("Energy Impact"), energyTooltip);
+ energyContainerElement.classList.add("energy");
+
+ let energyChartElement = energyContainerElement.parentElement;
+ let energySubtitleElement = energyChartElement.firstChild;
+ let energyInfoElement = energySubtitleElement.appendChild(document.createElement("span"));
+ energyInfoElement.className = "info";
+ energyInfoElement.textContent = "?";
+
+ this._energyInfoPopover = null;
+ this._energyInfoPopoverContentElement = null;
+ energyInfoElement.addEventListener("click", (event) => {
+ if (!this._energyInfoPopover)
+ this._energyInfoPopover = new WI.Popover;
+
+ if (!this._energyInfoPopoverContentElement) {
+ this._energyInfoPopoverContentElement = document.createElement("div");
+ this._energyInfoPopoverContentElement.className = "energy-info-popover-content";
+
+ const precision = 0;
+ let lowPercent = Number.percentageString(CPUTimelineView.lowEnergyThreshold / 100, precision);
+
+ let p1 = this._energyInfoPopoverContentElement.appendChild(document.createElement("p"));
+ p1.textContent = WI.UIString("Periods of high CPU utilization will rapidly drain battery. Strive to keep idle pages under %s average CPU utilization.").format(lowPercent);
+
+ let p2 = this._energyInfoPopoverContentElement.appendChild(document.createElement("p"));
+ p2.textContent = WI.UIString("There is an incurred energy penalty each time the page enters script. This commonly happens with timers, event handlers, and observers.");
+
+ let p3 = this._energyInfoPopoverContentElement.appendChild(document.createElement("p"));
+ p3.textContent = WI.UIString("To improve CPU utilization reduce or batch workloads when the page is not visible or during times when the page is not being interacted with.");
+ }
+
+ let isRTL = WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL;
+ let preferredEdges = isRTL ? [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_X] : [WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X];
+ let calculateTargetFrame = () => WI.Rect.rectFromClientRect(energyInfoElement.getBoundingClientRect()).pad(3);
+
+ this._energyInfoPopover.presentNewContentWithFrame(this._energyInfoPopoverContentElement, calculateTargetFrame(), preferredEdges);
+ this._energyInfoPopover.windowResizeHandler = () => {
+ this._energyInfoPopover.present(calculateTargetFrame(), preferredEdges);
+ };
+ });
+
+ this._energyChart = new WI.GaugeChart({
+ height: 110,
+ strokeWidth: 20,
+ segments: [
+ {className: "low", limit: 10},
+ {className: "medium", limit: 80},
+ {className: "high", limit: 100},
+ ]
+ });
+ this.addSubview(this._energyChart);
+ energyContainerElement.appendChild(this._energyChart.element);
+
+ let energyTextContainerElement = energyContainerElement.appendChild(document.createElement("div"));
+
+ this._energyImpactLabelElement = energyTextContainerElement.appendChild(document.createElement("div"));
+ this._energyImpactLabelElement.className = "energy-impact";
+
+ this._energyImpactNumberElement = energyTextContainerElement.appendChild(document.createElement("div"));
+ this._energyImpactNumberElement.className = "energy-impact-number";
+
+ this._energyImpactDurationElement = energyTextContainerElement.appendChild(document.createElement("div"));
+ this._energyImpactDurationElement.className = "energy-impact-number";
+
let detailsContainerElement = contentElement.appendChild(document.createElement("div"));
detailsContainerElement.classList.add("details");
@@ -255,6 +332,7 @@
let graphStartTime = this.startTime;
let graphEndTime = this.endTime;
let visibleEndTime = Math.min(this.endTime, this.currentTime);
+ let visibleDuration = visibleEndTime - graphStartTime;
let discontinuities = this._recording.discontinuitiesInTimeRange(graphStartTime, visibleEndTime);
let originalDiscontinuities = discontinuities.slice();
@@ -560,10 +638,64 @@
let graphWidth = (graphEndTime - graphStartTime) / secondsPerPixel;
let size = new WI.Size(graphWidth, CPUTimelineView.indicatorViewHeight);
this._mainThreadWorkIndicatorView.updateChart(samplingData.samples, size, visibleEndTime, xScaleIndicatorRange);
+
+ this._layoutEnergyChart(average, visibleDuration);
}
// Private
+ _layoutEnergyChart(average, visibleDuration)
+ {
+ // The lower the bias value [0..1], the more it increases the skew towards rangeHigh.
+ function mapWithBias(value, rangeLow, rangeHigh, outputRangeLow, outputRangeHigh, bias) {
+ console.assert(value >= rangeLow && value <= rangeHigh, "value was not in range.", value);
+ let percentInRange = (value - rangeLow) / (rangeHigh - rangeLow);
+ let skewedPercent = Math.pow(percentInRange, bias);
+ let valueInOutputRange = (skewedPercent * (outputRangeHigh - outputRangeLow)) + outputRangeLow;
+ return valueInOutputRange;
+ }
+
+ this._clearEnergyImpactText();
+
+ if (average === 0) {
+ // Zero. (0% CPU, mapped to 0)
+ this._energyImpactLabelElement.textContent = WI.UIString("Low");
+ this._energyImpactLabelElement.classList.add("low");
+ this._energyChart.value = 0;
+ } else if (average <= CPUTimelineView.lowEnergyThreshold) {
+ // Low. (<=3% CPU, mapped to 0-10)
+ this._energyImpactLabelElement.textContent = WI.UIString("Low");
+ this._energyImpactLabelElement.classList.add("low");
+ this._energyChart.value = mapWithBias(average, 0, CPUTimelineView.lowEnergyThreshold, 0, 10, 0.85);
+ } else if (average <= CPUTimelineView. mediumEnergyThreshold) {
+ // Medium (3%-90% CPU, mapped to 10-80)
+ this._energyImpactLabelElement.textContent = WI.UIString("Medium");
+ this._energyImpactLabelElement.classList.add("medium");
+ this._energyChart.value = mapWithBias(average, CPUTimelineView.lowEnergyThreshold, CPUTimelineView.mediumEnergyThreshold, 10, 80, 0.6);
+ } else if (average < CPUTimelineView. highEnergyThreshold) {
+ // High. (50-150% CPU, mapped to 80-100)
+ this._energyImpactLabelElement.textContent = WI.UIString("High");
+ this._energyImpactLabelElement.classList.add("high");
+ this._energyChart.value = mapWithBias(average, CPUTimelineView.mediumEnergyThreshold, CPUTimelineView.highEnergyThreshold, 80, 100, 0.9);
+ } else {
+ // Very High. (>150% CPU, mapped to 100)
+ this._energyImpactLabelElement.textContent = WI.UIString("Very High");
+ this._energyImpactLabelElement.classList.add("high");
+ this._energyChart.value = 100;
+ }
+
+ this._energyChart.needsLayout();
+
+ this._energyImpactNumberElement.textContent = WI.UIString("Average CPU: %s").format(Number.percentageString(average / 100));
+
+ if (visibleDuration < 5)
+ this._energyImpactDurationElement.textContent = WI.UIString("Duration: %s").format(WI.UIString("Short"));
+ else {
+ let durationDisplayString = Math.floor(visibleDuration) + "s";
+ this._energyImpactDurationElement.textContent = WI.UIString("Duration: %s").format(durationDisplayString);
+ }
+ }
+
_computeSamplingData(startTime, endTime)
{
// Compute per-millisecond samples of what the main thread was doing.
@@ -726,6 +858,14 @@
this._workerViews = [];
}
+ _clearEnergyImpactText()
+ {
+ this._energyImpactLabelElement.classList.remove("low", "medium", "high");
+ this._energyImpactLabelElement.textContent = emDash;
+ this._energyImpactNumberElement.textContent = "";
+ this._energyImpactDurationElement.textContent = "";
+ }
+
_clearBreakdownLegend()
{
this._breakdownLegendScriptElement.textContent = emDash;
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js (242299 => 242300)
--- trunk/Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js 2019-03-02 01:05:30 UTC (rev 242299)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js 2019-03-02 01:18:13 UTC (rev 242300)
@@ -312,7 +312,7 @@
return [
"M", x1, y1, // Starting position.
"A", r1, r1, 0, largeArcFlag, 1, x2, y2, // Draw outer arc.
- "L", x3, y3, // Connect outer and innner arcs.
+ "L", x3, y3, // Connect outer and inner arcs.
"A", r2, r2, 0, largeArcFlag, 0, x4, y4, // Draw inner arc.
"Z" // Close path.
].join(" ");
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CircleChart.js (242299 => 242300)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CircleChart.js 2019-03-02 01:05:30 UTC (rev 242299)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CircleChart.js 2019-03-02 01:18:13 UTC (rev 242300)
@@ -201,7 +201,7 @@
return [
"M", x1, y1, // Starting position.
"A", r1, r1, 0, largeArcFlag, 1, x2, y2, // Draw outer arc.
- "L", x3, y3, // Connect outer and innner arcs.
+ "L", x3, y3, // Connect outer and inner arcs.
"A", r2, r2, 0, largeArcFlag, 0, x4, y4, // Draw inner arc.
"Z" // Close path.
].join(" ");
Added: trunk/Source/WebInspectorUI/UserInterface/Views/GaugeChart.css (0 => 242300)
--- trunk/Source/WebInspectorUI/UserInterface/Views/GaugeChart.css (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/GaugeChart.css 2019-03-02 01:18:13 UTC (rev 242300)
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+.gauge-chart {
+ position: relative;
+}
+
+body[dir=rtl] .gauge-chart {
+ transform: scaleX(-1);
+}
+
+.gauge-chart > svg > path,
+.gauge-chart > svg > polygon {
+ stroke-width: 1;
+ transition-property: transform, fill, stroke;
+ transition-duration: 0.2s;
+}
+
+.gauge-chart:not(.empty) > svg > path {
+ fill: var(--gauge-chart-path-fill-color, hsla(0, 0%, 0%, 0.02));
+ stroke: var(--gauge-chart-path-stroke-color, hsla(0, 0%, var(--foreground-lightness), 0.1));
+}
+
+.gauge-chart:not(.empty) > svg > .needle {
+ fill: var(--gauge-chart-needle-fill-color, gray);
+ stroke: var(--gauge-chart-needle-stroke-color, gray);
+}
+
+.gauge-chart.empty > svg > path {
+ fill: hsla(0, 0%, 0%, 0.02);
+ stroke: hsla(0, 0%, var(--foreground-lightness), 0.1);
+}
+
+.gauge-chart.empty > svg > .needle {
+ fill: hsl(0, 0%, 88%);
+ stroke: hsla(0, 0%, var(--foreground-lightness), 0.1);
+}
+
+@media (prefers-color-scheme: dark) {
+ .gauge-chart.empty > svg > .needle {
+ fill: hsl(0, 0%, 20%);
+ }
+}
Added: trunk/Source/WebInspectorUI/UserInterface/Views/GaugeChart.js (0 => 242300)
--- trunk/Source/WebInspectorUI/UserInterface/Views/GaugeChart.js (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/GaugeChart.js 2019-03-02 01:18:13 UTC (rev 242300)
@@ -0,0 +1,200 @@
+/*
+ * 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.
+ */
+
+// GaugeChart creates a semi-circle gauge chart with colored segments.
+//
+// Initialize the chart with a semi-circle height, stroke width, and segments.
+// The class names you provide for the segments will allow you to style them
+// and the limit (0 - 100) is the upper percentage value where that segment
+// ends. You can update the chart with new current needle value at any time.
+//
+// SVG:
+//
+// - There is a path for each segment. Note there is a small includes a
+// buffer between segments, so they should be more than a few # apart.
+// - There is a single polygon for the needle value.
+//
+// <div class="gauge-chart">
+// <svg width="204" height="110" viewBox="0 0 204 110">
+// <path class="segment segment-class-name-1" d="..."/>
+// <path class="segment segment-class-name-2" d="..."/>
+// ...
+// <polygon class="needle" points="..."/>
+// </svg>
+// </div>
+
+WI.GaugeChart = class GaugeChart extends WI.View
+{
+ constructor({height, strokeWidth, segments})
+ {
+ super();
+
+ strokeWidth = strokeWidth || 10;
+
+ this._needleValue = null;
+
+ const needleOverhangSpace = 10; // Distance the needle goes past the outer circle edge.
+ const needleUnderhangSpace = 8; // Space allowed beneath the graph so a horizontal needle lines up.
+
+ this._center = height - needleUnderhangSpace;
+ this._radius = height - needleUnderhangSpace - needleOverhangSpace - 1;
+ this._innerRadius = Math.floor(this._radius - strokeWidth);
+
+ let width = (this._radius + needleOverhangSpace + 1) * 2;
+ this._size = new WI.Size(width, height);
+
+ console.assert(!this._segments, "Set segments only once");
+ console.assert(segments.length >= 1, "Need at least one segment");
+ console.assert(this._validateSegments(segments));
+
+ this._segments = segments;
+
+ this.element.classList.add("gauge-chart");
+
+ this._chartElement = this.element.appendChild(createSVGElement("svg"));
+ this._chartElement.setAttribute("width", width);
+ this._chartElement.setAttribute("height", height);
+ this._chartElement.setAttribute("viewBox", `0 0 ${width} ${height}`);
+
+ this._needleElement = null;
+ }
+
+ // Public
+
+ get size() { return this._size; }
+ get segments() { return this._segments; }
+
+ get value()
+ {
+ return this._needleValue;
+ }
+
+ set value(value)
+ {
+ console.assert(value >= 0 && value <= 100, "value should be between 0 and 100.", value);
+
+ this._needleValue = value;
+ }
+
+ clear()
+ {
+ this._needleValue = null;
+ }
+
+ // Protected
+
+ initialLayout()
+ {
+ super.initialLayout();
+
+ let startAngle = Math.PI;
+
+ const _onePercentAngle_ = Math.PI / 100;
+
+ for (let {className, limit} of this._segments) {
+ let offset = limit === 100 ? 0 : 1;
+ let endAngle = Math.PI + (((limit - offset) / 100) * Math.PI);
+
+ let pathElement = this._chartElement.appendChild(createSVGElement("path"));
+ pathElement.classList.add("segment", className);
+ pathElement.setAttribute("d", this._createSegmentPathData(this._center, startAngle, endAngle, this._radius, this._innerRadius));
+
+ startAngle = endAngle + onePercentAngle;
+ }
+
+ const needlePointExtraDraw = 0.5; // Draw a fat tip to the needle.
+ const needleBaseExtraDraw = 4.5; // Draw a fat base to the needle.
+ const needleUnderhangDraw = 6; // Draw the needle underhanging the base of the graph.
+
+ let midX = this.size.width / 2;
+ let midY = this._center;
+
+ this._needleElement = this._chartElement.appendChild(createSVGElement("polygon"));
+ this._needleElement.classList.add("needle");
+ this._needleElement.setAttribute("points", `0,${midY + needlePointExtraDraw}, 0,${midY - needlePointExtraDraw} ${midX + needleUnderhangDraw},${midY - needleBaseExtraDraw} ${midX + needleUnderhangDraw},${midY + needleBaseExtraDraw}`);
+ this._needleElement.style.transformOrigin = `${midX}px ${midY}px`;
+ }
+
+ layout()
+ {
+ super.layout();
+
+ if (this.layoutReason === WI.View.LayoutReason.Resize)
+ return;
+
+ let empty = this._needleValue === null;
+ this.element.classList.toggle("empty", empty);
+
+ let value = empty ? 0 : this._needleValue;
+ let degrees = 180 * (value / 100); // 0-100% mapped to 0-180deg.
+ this._needleElement.style.transform = `rotate(${degrees}deg)`;
+ }
+
+ // Private
+
+ _validateSegments(segments)
+ {
+ let lastLimit = -1;
+
+ for (let {className, limit} of segments) {
+ console.assert(limit >= 1 && limit <= 100, "limit should be between 1 and 100", limit);
+ console.assert(limit >= (lastLimit + 1), "limits should always increase between segments");
+ lastLimit = limit;
+ }
+
+ return true;
+ }
+
+ _createSegmentPathData(c, a1, a2, r1, r2)
+ {
+ const startIndicatorUnderhang = 7;
+ let r3 = (r2 - startIndicatorUnderhang);
+ let _onePercentArc_ = Math.PI / 100;
+ let largeArcFlag = ((a2 - a1) % (Math.PI * 2)) > Math.PI ? 1 : 0;
+
+ let x1 = c + Math.cos(a1) * r1,
+ y1 = c + Math.sin(a1) * r1,
+ x2 = c + Math.cos(a2) * r1,
+ y2 = c + Math.sin(a2) * r1,
+ x3 = c + Math.cos(a2) * r2,
+ y3 = c + Math.sin(a2) * r2,
+ x4 = c + Math.cos(a1 + onePercentArc) * r2,
+ y4 = c + Math.sin(a1 + onePercentArc) * r2,
+ x5 = c + Math.cos(a1 + onePercentArc) * r3,
+ y5 = c + Math.sin(a1 + onePercentArc) * r3,
+ x6 = c + Math.cos(a1) * r3,
+ y6 = c + Math.sin(a1) * r3;
+
+ return [
+ "M", x1, y1, // Starting position.
+ "A", r1, r1, 0, largeArcFlag, 1, x2, y2, // Draw outer arc.
+ "L", x3, y3, // Connect outer and inner arcs.
+ "A", r2, r2, 0, largeArcFlag, 0, x4, y4, // Draw inner arc.
+ "L", x5, y5, // Extend inner arc to center for start indicator.
+ "A", r3, r3, 0, largeArcFlag, 0, x6, y6, // Draw final inner arc for start indicator.
+ "Z" // Close path.
+ ].join(" ");
+ }
+};
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css (242299 => 242300)
--- trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css 2019-03-02 01:05:30 UTC (rev 242299)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css 2019-03-02 01:18:13 UTC (rev 242300)
@@ -142,6 +142,10 @@
--cpu-paint-fill-color: hsl(76, 49%, 75%);
--cpu-paint-stroke-color: hsl(79, 45%, 50%);
+ --cpu-low-color: hsl(110, 52%, 56%);
+ --cpu-medium-color: hsl(46, 91%, 62%);
+ --cpu-high-color: hsl(6, 79%, 57%);
+
--network-header-color: hsl(204, 52%, 55%);
--network-system-color: hsl(79, 32%, 50%);
--network-pseudo-header-color: hsl(312, 35%, 51%);
@@ -284,6 +288,10 @@
--network-pseudo-header-color: hsl(312, 55%, 61%);
--network-error-color: hsl(0, 54%, 55%);
+ --cpu-low-color: hsl(110, 52%, 56%);
+ --cpu-medium-color: hsl(46, 91%, 62%);
+ --cpu-high-color: hsl(6, 79%, 57%);
+
--even-zebra-stripe-row-background-color: var(--background-color);
--odd-zebra-stripe-row-background-color: var(--background-color-secondary);
--transparent-stripe-background-gradient: linear-gradient(to bottom, transparent, transparent 50%, hsla(0, 0%, 100%, 0.03) 50%, hsla(0, 0%, 100%, 0.03)) top left / 100% 40px;