Diff
Modified: trunk/LayoutTests/ChangeLog (242561 => 242562)
--- trunk/LayoutTests/ChangeLog 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/LayoutTests/ChangeLog 2019-03-06 20:38:43 UTC (rev 242562)
@@ -1,3 +1,15 @@
+2019-03-06 Joseph Pecoraro <[email protected]>
+
+ Web Inspector: CPU Usage Timeline - Statistics and Sources sections
+ https://bugs.webkit.org/show_bug.cgi?id=195202
+
+ Reviewed by Devin Rousso.
+
+ * inspector/unit-tests/map-utilities-expected.txt: Added.
+ * inspector/unit-tests/map-utilities.html: Added.
+ * inspector/unit-tests/set-utilities-expected.txt:
+ * inspector/unit-tests/set-utilities.html:
+
2019-03-06 Wenson Hsieh <[email protected]>
[iOS] Frequent 1 second IPC deadlocks when showing a paste callout
Added: trunk/LayoutTests/inspector/unit-tests/map-utilities-expected.txt (0 => 242562)
--- trunk/LayoutTests/inspector/unit-tests/map-utilities-expected.txt (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/map-utilities-expected.txt 2019-03-06 20:38:43 UTC (rev 242562)
@@ -0,0 +1,23 @@
+
+== Running test suite: Map
+-- Running test case: Map.fromObject
+PASS: Map from simple object should have size 2.
+PASS: Map from simple object should have keys: key1 and key2.
+PASS: Map from simple object should have values: value1 and value2.
+PASS: Map from empty object should be empty.
+
+-- Running test case: Map.prototype.take
+PASS: Map has `key`.
+PASS: Map has `key` => `value`.
+PASS: Map take `key` => `value`.
+PASS: Map does not have `key`.
+PASS: Map has `key` => `undefined`.
+PASS: Map take `doesNotExistKey` => `undefined`.
+
+-- Running test case: Map.prototype.getOrInitialize
+PASS: Map does not have `key`.
+PASS: Map should have initialized `key` with `value`.
+PASS: Map does have `key` => `value`.
+PASS: Map should get `key` with `value` without re-initializing.
+PASS: Map still has `key` => `value`.
+
Added: trunk/LayoutTests/inspector/unit-tests/map-utilities.html (0 => 242562)
--- trunk/LayoutTests/inspector/unit-tests/map-utilities.html (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/map-utilities.html 2019-03-06 20:38:43 UTC (rev 242562)
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+ let suite = InspectorTest.createSyncSuite("Map");
+
+ suite.addTestCase({
+ name: "Map.fromObject",
+ test() {
+ const key1 = "key1";
+ const key2 = "key2";
+ const value1 = "value1";
+ const value2 = 200;
+ const object = {
+ [key1]: value1,
+ [key2]: value2,
+ };
+
+ let map = Map.fromObject(object);
+ InspectorTest.expectEqual(map.size, 2, "Map from simple object should have size 2.");
+ InspectorTest.expectThat(Array.shallowEqual(Array.from(map.keys()), [key1, key2]), "Map from simple object should have keys: key1 and key2.");
+ InspectorTest.expectThat(Array.shallowEqual(Array.from(map.values()), [value1, value2]), "Map from simple object should have values: value1 and value2.");
+
+ let emptyMap = Map.fromObject({});
+ InspectorTest.expectEqual(emptyMap.size, 0, "Map from empty object should be empty.");
+ }
+ });
+
+ suite.addTestCase({
+ name: "Map.prototype.take",
+ test() {
+ const key = "key";
+ const value = "value";
+
+ let map = new Map;
+ map.set(key, value);
+ InspectorTest.expectTrue(map.has(key), "Map has `key`.");
+ InspectorTest.expectEqual(map.get(key), value, "Map has `key` => `value`.");
+ InspectorTest.expectEqual(map.take(key), value, "Map take `key` => `value`.");
+ InspectorTest.expectFalse(map.has(key), "Map does not have `key`.");
+ InspectorTest.expectEqual(map.get(key), undefined, "Map has `key` => `undefined`.");
+ InspectorTest.expectEqual(map.take("doesNotExistKey"), undefined, "Map take `doesNotExistKey` => `undefined`.");
+ }
+ });
+
+ suite.addTestCase({
+ name: "Map.prototype.getOrInitialize",
+ test() {
+ const key = "key";
+ const value = "value";
+ const value2 = "value2";
+
+ let map = new Map;
+ InspectorTest.expectEqual(map.get(key), undefined, "Map does not have `key`.");
+ InspectorTest.expectEqual(map.getOrInitialize(key, value), value, "Map should have initialized `key` with `value`.");
+ InspectorTest.expectEqual(map.get(key), value, "Map does have `key` => `value`.");
+ InspectorTest.expectEqual(map.getOrInitialize(key, value2), value, "Map should get `key` with `value` without re-initializing.");
+ InspectorTest.expectEqual(map.get(key), value, "Map still has `key` => `value`.");
+ }
+ });
+
+ suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onLoad_="runTest()">
+</body>
+</html>
Modified: trunk/LayoutTests/inspector/unit-tests/set-utilities-expected.txt (242561 => 242562)
--- trunk/LayoutTests/inspector/unit-tests/set-utilities-expected.txt 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/LayoutTests/inspector/unit-tests/set-utilities-expected.txt 2019-03-06 20:38:43 UTC (rev 242562)
@@ -1,5 +1,5 @@
-== Running test suite: SetUtilities
+== Running test suite: Set
-- Running test case: Set.prototype.intersects
PASS: an empty set should not intersect another empty set.
PASS: a non-empty set should not intersect an empty set.
Modified: trunk/LayoutTests/inspector/unit-tests/set-utilities.html (242561 => 242562)
--- trunk/LayoutTests/inspector/unit-tests/set-utilities.html 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/LayoutTests/inspector/unit-tests/set-utilities.html 2019-03-06 20:38:43 UTC (rev 242562)
@@ -5,7 +5,7 @@
<script>
function test()
{
- let suite = InspectorTest.createSyncSuite("SetUtilities");
+ let suite = InspectorTest.createSyncSuite("Set");
suite.addTestCase({
name: "Set.prototype.intersects",
@@ -28,8 +28,6 @@
testFalse([1, "a", object1], [2, "b", object2], "a set should not intersect another set with different values.");
testTrue([1, "a", object1], [1, 3, "a", "c", object1, object3], "a set should intersect another set with same and additional values.");
testTrue([1, 2, "a", "b", object1, object2], [1, 3, "a", "c", object1, object3], "a set should intersect another set with same and different values.");
-
- return true;
}
});
@@ -54,8 +52,6 @@
testFalse([1, "a", object1], [2, "b", object2], "a set should not be a subset of another set with different values.");
testTrue([1, "a", object1], [1, 3, "a", "c", object1, object3], "a set should be a subset of another set with same and additional values.");
testFalse([1, 2, "a", "b", object1, object2], [1, 3, "a", "c", object1, object3], "a set should not be a subset of another set with same and different values.");
-
- return true;
}
});
@@ -79,8 +75,6 @@
testTrue([1, "a", object1], [object1, 1, "a"], "a set should be equal to another set with the same values in a different order.");
testFalse([1, "a", object1], [2, "b", object2], "a set should not be a equal to another set with different values.");
testFalse([1, 2, "a", "b", object1, object2], [1, 3, "a", "c", object1, object3], "a set should not be equal to another set with same and different values.");
-
- return true;
}
});
@@ -121,8 +115,6 @@
bValues: [2, 3, 4],
expectedDifference: [1],
});
-
- return true;
}
});
Modified: trunk/Source/WebInspectorUI/ChangeLog (242561 => 242562)
--- trunk/Source/WebInspectorUI/ChangeLog 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/Source/WebInspectorUI/ChangeLog 2019-03-06 20:38:43 UTC (rev 242562)
@@ -1,5 +1,69 @@
2019-03-06 Joseph Pecoraro <[email protected]>
+ Web Inspector: CPU Usage Timeline - Statistics and Sources sections
+ https://bugs.webkit.org/show_bug.cgi?id=195202
+
+ Reviewed by Devin Rousso.
+
+ * Localizations/en.lproj/localizedStrings.js:
+ New strings.
+
+ * UserInterface/Base/Utilities.js:
+ (Map.prototype.getOrInitialize):
+ Helper to get and if not found initialize with a value.
+
+ * UserInterface/Views/CPUTimelineView.css:
+ (.timeline-view.cpu > .content > .overview > .chart > .container.stats):
+ (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table):
+ (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > th):
+ (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.number):
+ (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.label):
+ (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .show-more):
+ (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter):
+ (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter:hover):
+ (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .active):
+ (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .active + .active):
+ (@media (prefers-color-scheme: dark)):
+ Colors for the statistics sections.
+
+ * UserInterface/Views/CPUTimelineView.js:
+ (WI.CPUTimelineView):
+ (WI.CPUTimelineView.prototype.reset):
+ (WI.CPUTimelineView.prototype.clear):
+ (WI.CPUTimelineView.prototype._clearStatistics):
+ (WI.CPUTimelineView.prototype._clearSources):
+ Updates for additional sections.
+ Include a cache of the statisiticsData so we can relayout parts of the UI and
+ avoid an entire UI update.
+
+ (WI.CPUTimelineView.prototype.initialLayout):
+ (WI.CPUTimelineView.prototype._layoutBreakdownChart):
+ (WI.CPUTimelineView.prototype._layoutStatisticsAndSources):
+ (WI.CPUTimelineView.prototype._layoutStatisticsSection.createEllipsisElement):
+ (WI.CPUTimelineView.prototype._layoutStatisticsSection):
+ (WI.CPUTimelineView.prototype._layoutSourcesSection.firstNonNativeCallFrame):
+ (WI.CPUTimelineView.prototype._layoutSourcesSection.keyForSourceCodeLocation):
+ (WI.CPUTimelineView.prototype._layoutSourcesSection.labelForLocation):
+ (WI.CPUTimelineView.prototype._layoutSourcesSection.createEllipsisElement):
+ (WI.CPUTimelineView.prototype._layoutSourcesSection):
+ Extract layouts into helper methods to avoid an enormous layout method.
+
+ (WI.CPUTimelineView.prototype._computeSamplingData.incrementTypeCount):
+ (WI.CPUTimelineView.prototype._computeSamplingData):
+ Compute additional data when going through script events.
+
+ (WI.CPUTimelineView.prototype._resetSourcesFilters):
+ (WI.CPUTimelineView.prototype._addSourcesFilter):
+ (WI.CPUTimelineView.prototype._removeSourcesFilter):
+ (WI.CPUTimelineView.prototype._updateSourcesFilters):
+ Helpers for updating the source filters.
+
+ (WI.CPUTimelineView.prototype._createTableRow):
+ (WI.CPUTimelineView.prototype._insertTableRow):
+ Helpers for creating rows in the statistics / sources tables.
+
+2019-03-06 Joseph Pecoraro <[email protected]>
+
Web Inspector: Simplify chart <rect>s with x/y attributes instead of transform(x, y)
https://bugs.webkit.org/show_bug.cgi?id=195352
Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (242561 => 242562)
--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js 2019-03-06 20:38:43 UTC (rev 242562)
@@ -122,6 +122,8 @@
localizedStrings["Anonymous Scripts"] = "Anonymous Scripts";
localizedStrings["Anonymous StyleSheet %d"] = "Anonymous StyleSheet %d";
localizedStrings["Application Cache"] = "Application Cache";
+/* Approximate count of events */
+localizedStrings["Approximate Number"] = "~%s";
localizedStrings["Area"] = "Area";
localizedStrings["Assertion"] = "Assertion";
localizedStrings["Assertion Failed"] = "Assertion Failed";
@@ -427,8 +429,10 @@
localizedStrings["Event"] = "Event";
localizedStrings["Event Breakpoint\u2026"] = "Event Breakpoint\u2026";
localizedStrings["Event Dispatched"] = "Event Dispatched";
+localizedStrings["Event Handlers:"] = "Event Handlers:";
localizedStrings["Event Listeners"] = "Event Listeners";
localizedStrings["Events"] = "Events";
+localizedStrings["Events:"] = "Events:";
localizedStrings["Example: \u201C%s\u201D"] = "Example: \u201C%s\u201D";
localizedStrings["Exception with thrown value: %s"] = "Exception with thrown value: %s";
localizedStrings["Exited Full-Screen Mode"] = "Exited Full-Screen Mode";
@@ -457,6 +461,7 @@
localizedStrings["Filename"] = "Filename";
localizedStrings["Filter"] = "Filter";
localizedStrings["Filter Full URL"] = "Filter Full URL";
+localizedStrings["Filter:"] = "Filter:";
localizedStrings["Find Next (%s)"] = "Find Next (%s)";
localizedStrings["Find Previous (%s)"] = "Find Previous (%s)";
localizedStrings["Flows"] = "Flows";
@@ -686,6 +691,8 @@
localizedStrings["Object Graph"] = "Object Graph";
localizedStrings["Object Store"] = "Object Store";
localizedStrings["Observer Callback"] = "Observer Callback";
+localizedStrings["Observer Handlers:"] = "Observer Handlers:";
+localizedStrings["Observers:"] = "Observers:";
localizedStrings["Off"] = "Off";
localizedStrings["Once"] = "Once";
localizedStrings["Online"] = "Online";
@@ -853,6 +860,7 @@
localizedStrings["Screen Shot %s-%s-%s at %s.%s.%s"] = "Screen Shot %s-%s-%s at %s.%s.%s";
localizedStrings["Script"] = "Script";
localizedStrings["Script Element %d"] = "Script Element %d";
+localizedStrings["Script Entries:"] = "Script Entries:";
localizedStrings["Script Evaluated"] = "Script Evaluated";
localizedStrings["Scripts"] = "Scripts";
localizedStrings["Scroll Into View"] = "Scroll Into View";
@@ -956,6 +964,7 @@
localizedStrings["Start recording canvas actions.\nShift-click to record a single frame."] = "Start recording canvas actions.\nShift-click to record a single frame.";
localizedStrings["Start to Finish"] = "Start to Finish";
localizedStrings["State"] = "State";
+localizedStrings["Statistics"] = "Statistics";
localizedStrings["Status"] = "Status";
localizedStrings["Step"] = "Step";
localizedStrings["Step into (%s or %s)"] = "Step into (%s or %s)";
@@ -1033,6 +1042,7 @@
localizedStrings["Timer Fired"] = "Timer Fired";
localizedStrings["Timer Installed"] = "Timer Installed";
localizedStrings["Timer Removed"] = "Timer Removed";
+localizedStrings["Timers:"] = "Timers:";
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.";
@@ -1063,6 +1073,7 @@
localizedStrings["Uncaught Exceptions"] = "Uncaught Exceptions";
localizedStrings["Undefined custom element"] = "Undefined custom element";
localizedStrings["Unique"] = "Unique";
+localizedStrings["Unknown Location"] = "Unknown Location";
localizedStrings["Unknown error"] = "Unknown error";
localizedStrings["Unknown node"] = "Unknown node";
localizedStrings["Unsupported property name"] = "Unsupported property name";
Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js (242561 => 242562)
--- trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js 2019-03-06 20:38:43 UTC (rev 242562)
@@ -112,12 +112,27 @@
{
value(key)
{
- var deletedValue = this.get(key);
+ let deletedValue = this.get(key);
this.delete(key);
return deletedValue;
}
});
+Object.defineProperty(Map.prototype, "getOrInitialize",
+{
+ value(key, initialValue)
+ {
+ console.assert(initialValue !== undefined, "getOrInitialize should not be used with undefined.");
+
+ let value = this.get(key);
+ if (value)
+ return value;
+
+ this.set(key, initialValue);
+ return initialValue;
+ }
+});
+
Object.defineProperty(Set.prototype, "equals",
{
value(other)
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js (242561 => 242562)
--- trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js 2019-03-06 20:38:43 UTC (rev 242562)
@@ -108,10 +108,9 @@
return null;
// Return the first non-native code call frame as the initiator.
- for (var i = 0; i < this._callFrames.length; ++i) {
- if (this._callFrames[i].nativeCode)
- continue;
- return this._callFrames[i];
+ for (let frame of this._callFrames) {
+ if (!frame.nativeCode)
+ return frame;
}
return null;
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css (242561 => 242562)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css 2019-03-06 20:38:43 UTC (rev 242562)
@@ -45,9 +45,9 @@
width: 15px;
height: 15px;
-webkit-margin-start: 7px;
- color: white;
font-size: 12px;
- background-color: darkgray;
+ color: var(--gray-foreground-color);
+ background-color: var(--gray-background-color);
border-radius: 50%;
}
@@ -318,13 +318,80 @@
font-size: 14px;
}
+.timeline-view.cpu > .content > .overview > .chart > .container.stats {
+ padding: 0 5px;
+ white-space: nowrap;
+ -webkit-user-select: text;
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table {
+ overflow: hidden;
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > th {
+ text-align: end;
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.number {
+ min-width: 25px;
+ padding: 0px 2px;
+ text-align: end;
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.label {
+ text-align: start;
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .show-more {
+ cursor: pointer;
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .unknown {
+ color: var(--link-text-color);
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter-clear {
+ display: inline-block;
+ width: 13px;
+ height: 13px;
+ font-size: 12px;
+ color: var(--gray-foreground-color);
+ background-color: var(--gray-background-color);
+ border-radius: 50%;
+ line-height: 12px;
+ text-align: center;
+ cursor: pointer;
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter {
+ padding: 0 6px 1px;
+ font-size: 10px;
+ background-color: hsl(0, 0%, 85%);
+ border: 1px solid transparent;
+ border-radius: 3px;
+ cursor: pointer;
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table :matches(.filter, .filter-clear):hover {
+ opacity: 0.7;
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter.active {
+ color: var(--selected-foreground-color);
+ background-color: var(--selected-background-color);
+}
+
+.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter.active + .filter.active {
+ -webkit-margin-start: 3px;
+}
+
@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);
}
+
+ .timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter {
+ background-color: hsl(0, 0%, 33%);
+ }
}
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js (242561 => 242562)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js 2019-03-06 20:38:43 UTC (rev 242562)
@@ -35,6 +35,9 @@
this.element.classList.add("cpu");
+ this._statisticsData = null;
+ this._sectionLimit = CPUTimelineView.defaultSectionLimit;
+
timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._cpuTimelineRecordAdded, this);
}
@@ -63,6 +66,8 @@
static get mediumEnergyThreshold() { return 50; }
static get highEnergyThreshold() { return 150; }
+ static get defaultSectionLimit() { return 5; }
+
// Public
shown()
@@ -83,6 +88,8 @@
{
super.reset();
+ this._resetSourcesFilters();
+
this.clear();
}
@@ -99,6 +106,9 @@
this._energyChart.needsLayout();
this._clearEnergyImpactText();
+ this._clearStatistics();
+ this._clearSources();
+
function clearUsageView(view) {
view.clear();
@@ -115,6 +125,9 @@
this._removeWorkerThreadViews();
this._mainThreadWorkIndicatorView.clear();
+
+ this._statisticsData = null;
+ this._sectionLimit = CPUTimelineView.defaultSectionLimit;
}
// Protected
@@ -145,7 +158,8 @@
let chartSubtitleElement = chartElement.appendChild(document.createElement("div"));
chartSubtitleElement.classList.add("subtitle");
chartSubtitleElement.textContent = subtitle;
- chartSubtitleElement.title = tooltip;
+ if (tooltip)
+ chartSubtitleElement.title = tooltip;
let chartFlexContainerElement = chartElement.appendChild(document.createElement("div"));
chartFlexContainerElement.classList.add("container");
@@ -189,8 +203,7 @@
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);
+ let energyContainerElement = createChartContainer(overviewElement, WI.UIString("Energy Impact"), WI.UIString("Estimated energy impact."));
energyContainerElement.classList.add("energy");
let energyChartElement = energyContainerElement.parentElement;
@@ -288,7 +301,8 @@
this._threadsDetailsElement.open = WI.settings.cpuTimelineThreadDetailsExpanded.value;
this._threadsDetailsElement.addEventListener("toggle", (event) => {
WI.settings.cpuTimelineThreadDetailsExpanded.value = this._threadsDetailsElement.open;
- this.updateLayout();
+ if (this._threadsDetailsElement.open)
+ this.updateLayout(WI.CPUTimelineView.LayoutReason.Internal);
});
let threadsSubtitleElement = this._threadsDetailsElement.appendChild(document.createElement("summary"));
@@ -312,6 +326,78 @@
this._workerViews = [];
+ this._sourcesFilter = {
+ timer: new Set,
+ event: new Set,
+ observer: new Set,
+ };
+
+ let bottomOverviewElement = contentElement.appendChild(document.createElement("div"));
+ bottomOverviewElement.classList.add("overview");
+
+ let statisticsContainerElement = createChartContainer(bottomOverviewElement, WI.UIString("Statistics"));
+ statisticsContainerElement.classList.add("stats");
+
+ this._statisticsTable = statisticsContainerElement.appendChild(document.createElement("table"));
+ this._statisticsRows = [];
+
+ {
+ let {headerCell, numberCell} = this._createTableRow(this._statisticsTable);
+ headerCell.textContent = WI.UIString("Script Entries:");
+ this._scriptEntriesNumberElement = numberCell;
+ }
+
+ this._clearStatistics();
+
+ let bottomDividerElement = bottomOverviewElement.appendChild(document.createElement("div"));
+ bottomDividerElement.classList.add("divider");
+
+ let sourcesContainerElement = createChartContainer(bottomOverviewElement, WI.UIString("Sources"));
+ sourcesContainerElement.classList.add("stats");
+
+ this._sourcesTable = sourcesContainerElement.appendChild(document.createElement("table"));
+ this._sourcesRows = [];
+
+ {
+ let {row, headerCell, numberCell, labelCell} = this._createTableRow(this._sourcesTable);
+ headerCell.textContent = WI.UIString("Filter:");
+ this._sourcesFilterRow = row;
+ this._sourcesFilterRow.hidden = true;
+ this._sourcesFilterNumberElement = numberCell;
+ this._sourcesFilterLabelElement = labelCell;
+
+ let filterClearElement = numberCell.appendChild(document.createElement("span"));
+ filterClearElement.className = "filter-clear";
+ filterClearElement.textContent = multiplicationSign;
+ filterClearElement.addEventListener("click", (event) => {
+ this._resetSourcesFilters();
+ this._layoutStatisticsAndSources();
+ });
+ }
+ {
+ let {row, headerCell, numberCell, labelCell} = this._createTableRow(this._sourcesTable);
+ headerCell.textContent = WI.UIString("Timers:");
+ this._timerInstallationsRow = row;
+ this._timerInstallationsNumberElement = numberCell;
+ this._timerInstallationsLabelElement = labelCell;
+ }
+ {
+ let {row, headerCell, numberCell, labelCell} = this._createTableRow(this._sourcesTable);
+ headerCell.textContent = WI.UIString("Event Handlers:");
+ this._eventHandlersRow = row;
+ this._eventHandlersNumberElement = numberCell;
+ this._eventHandlersLabelElement = labelCell;
+ }
+ {
+ let {row, headerCell, numberCell, labelCell} = this._createTableRow(this._sourcesTable);
+ headerCell.textContent = WI.UIString("Observer Handlers:");
+ this._observerHandlersRow = row;
+ this._observerHandlersNumberElement = numberCell;
+ this._observerHandlersLabelElement = labelCell;
+ }
+
+ this._clearSources();
+
this.element.addEventListener("mousemove", this._handleGraphMouseMove.bind(this));
}
@@ -320,6 +406,9 @@
if (this.layoutReason === WI.View.LayoutReason.Resize)
return;
+ if (this.layoutReason !== WI.CPUTimelineView.LayoutReason.Internal)
+ this._sectionLimit = CPUTimelineView.defaultSectionLimit;
+
// Always update timeline ruler.
this._timelineRuler.zeroTime = this.zeroTime;
this._timelineRuler.startTime = this.startTime;
@@ -345,38 +434,10 @@
return;
}
- let samplingData = this._computeSamplingData(graphStartTime, visibleEndTime);
- let nonIdleSamplesCount = samplingData.samples.length - samplingData.samplesIdle;
- if (!nonIdleSamplesCount) {
- this._breakdownChart.clear();
- this._breakdownChart.needsLayout();
- this._clearBreakdownLegend();
- } else {
- let percentScript = samplingData.samplesScript / nonIdleSamplesCount;
- let percentLayout = samplingData.samplesLayout / nonIdleSamplesCount;
- let percentPaint = samplingData.samplesPaint / nonIdleSamplesCount;
- let percentStyle = samplingData.samplesStyle / nonIdleSamplesCount;
+ this._statisticsData = this._computeStatisticsData(graphStartTime, visibleEndTime);
+ this._layoutBreakdownChart();
+ this._layoutStatisticsAndSources();
- this._breakdownLegendScriptElement.textContent = `${Number.percentageString(percentScript)} (${samplingData.samplesScript})`;
- this._breakdownLegendLayoutElement.textContent = `${Number.percentageString(percentLayout)} (${samplingData.samplesLayout})`;
- this._breakdownLegendPaintElement.textContent = `${Number.percentageString(percentPaint)} (${samplingData.samplesPaint})`;
- this._breakdownLegendStyleElement.textContent = `${Number.percentageString(percentStyle)} (${samplingData.samplesStyle})`;
-
- this._breakdownChart.values = [percentScript * 100, percentLayout * 100, percentPaint * 100, percentStyle * 100];
- this._breakdownChart.needsLayout();
-
- let centerElement = this._breakdownChart.centerElement;
- let samplesElement = centerElement.firstChild;
- if (!samplesElement) {
- samplesElement = centerElement.appendChild(document.createElement("div"));
- samplesElement.classList.add("samples");
- samplesElement.title = WI.UIString("Time spent on the main thread");
- }
-
- let millisecondsStringNoDecimal = WI.UIString("%.0fms").format(nonIdleSamplesCount);
- samplesElement.textContent = millisecondsStringNoDecimal;
- }
-
let dataPoints = [];
let workersDataMap = new Map;
let workersSeenInCurrentRecord = new Set;
@@ -637,7 +698,7 @@
let graphWidth = (graphEndTime - graphStartTime) / secondsPerPixel;
let size = new WI.Size(graphWidth, CPUTimelineView.indicatorViewHeight);
- this._mainThreadWorkIndicatorView.updateChart(samplingData.samples, size, visibleEndTime, xScaleIndicatorRange);
+ this._mainThreadWorkIndicatorView.updateChart(this._statisticsData.samples, size, visibleEndTime, xScaleIndicatorRange);
this._layoutEnergyChart(average, visibleDuration);
}
@@ -644,6 +705,353 @@
// Private
+ _layoutBreakdownChart()
+ {
+ let {samples, samplesScript, samplesLayout, samplesPaint, samplesStyle, samplesIdle} = this._statisticsData;
+
+ let nonIdleSamplesCount = samples.length - samplesIdle;
+ if (!nonIdleSamplesCount) {
+ this._breakdownChart.clear();
+ this._breakdownChart.needsLayout();
+ this._clearBreakdownLegend();
+ return;
+ }
+
+ let percentScript = samplesScript / nonIdleSamplesCount;
+ let percentLayout = samplesLayout / nonIdleSamplesCount;
+ let percentPaint = samplesPaint / nonIdleSamplesCount;
+ let percentStyle = samplesStyle / nonIdleSamplesCount;
+
+ this._breakdownLegendScriptElement.textContent = `${Number.percentageString(percentScript)} (${samplesScript})`;
+ this._breakdownLegendLayoutElement.textContent = `${Number.percentageString(percentLayout)} (${samplesLayout})`;
+ this._breakdownLegendPaintElement.textContent = `${Number.percentageString(percentPaint)} (${samplesPaint})`;
+ this._breakdownLegendStyleElement.textContent = `${Number.percentageString(percentStyle)} (${samplesStyle})`;
+
+ this._breakdownChart.values = [percentScript * 100, percentLayout * 100, percentPaint * 100, percentStyle * 100];
+ this._breakdownChart.needsLayout();
+
+ let centerElement = this._breakdownChart.centerElement;
+ let samplesElement = centerElement.firstChild;
+ if (!samplesElement) {
+ samplesElement = centerElement.appendChild(document.createElement("div"));
+ samplesElement.classList.add("samples");
+ samplesElement.title = WI.UIString("Time spent on the main thread");
+ }
+
+ let millisecondsStringNoDecimal = WI.UIString("%.0fms").format(nonIdleSamplesCount);
+ samplesElement.textContent = millisecondsStringNoDecimal;
+ }
+
+ _layoutStatisticsAndSources()
+ {
+ this._layoutStatisticsSection();
+ this._layoutSourcesSection();
+ }
+
+ _layoutStatisticsSection()
+ {
+ let statistics = this._statisticsData;
+
+ this._clearStatistics();
+
+ this._scriptEntriesNumberElement.textContent = statistics.scriptEntries;
+
+ let createFilterElement = (type, name) => {
+ let span = document.createElement("span");
+ span.className = "filter";
+ span.textContent = name;
+ span.addEventListener("mouseup", (event) => {
+ if (span.classList.contains("active"))
+ this._removeSourcesFilter(type, name);
+ else
+ this._addSourcesFilter(type, name);
+
+ this._layoutStatisticsAndSources();
+ });
+
+ span.classList.toggle("active", this._sourcesFilter[type].has(name));
+
+ return span;
+ };
+
+ let expandAllSections = () => {
+ this._sectionLimit = Infinity;
+ this._layoutStatisticsAndSources();
+ };
+
+ function createEllipsisElement() {
+ let span = document.createElement("span");
+ span.className = "show-more";
+ span.role = "button";
+ span.textContent = ellipsis;
+ span.addEventListener("click", (event) => {
+ expandAllSections();
+ });
+ return span;
+ }
+
+ // Sort a Map of key => count values in descending order.
+ function sortMapByEntryCount(map) {
+ let entries = Array.from(map);
+ entries.sort((entryA, entryB) => entryB[1] - entryA[1]);
+ return new Map(entries);
+ }
+
+ if (statistics.timerTypes.size) {
+ let i = 0;
+ let sorted = sortMapByEntryCount(statistics.timerTypes);
+ for (let [timerType, count] of sorted) {
+ let headerValue = i === 0 ? WI.UIString("Timers:") : "";
+ let timerTypeElement = createFilterElement("timer", timerType);
+ this._insertTableRow(this._statisticsTable, this._statisticsRows, {headerValue, numberValue: count, labelValue: timerTypeElement});
+
+ if (++i === this._sectionLimit && sorted.size > this._sectionLimit) {
+ this._insertTableRow(this._statisticsTable, this._statisticsRows, {labelValue: createEllipsisElement()});
+ break;
+ }
+ }
+ }
+
+ if (statistics.eventTypes.size) {
+ let i = 0;
+ let sorted = sortMapByEntryCount(statistics.eventTypes);
+ for (let [eventType, count] of sorted) {
+ let headerValue = i === 0 ? WI.UIString("Events:") : "";
+ let eventTypeElement = createFilterElement("event", eventType);
+ this._insertTableRow(this._statisticsTable, this._statisticsRows, {headerValue, numberValue: count, labelValue: eventTypeElement});
+
+ if (++i === this._sectionLimit && sorted.size > this._sectionLimit) {
+ this._insertTableRow(this._statisticsTable, this._statisticsRows, {labelValue: createEllipsisElement()});
+ break;
+ }
+ }
+ }
+
+ if (statistics.observerTypes.size) {
+ let i = 0;
+ let sorted = sortMapByEntryCount(statistics.observerTypes);
+ for (let [observerType, count] of sorted) {
+ let headerValue = i === 0 ? WI.UIString("Observers:") : "";
+ let observerTypeElement = createFilterElement("observer", observerType);
+ this._insertTableRow(this._statisticsTable, this._statisticsRows, {headerValue, numberValue: count, labelValue: observerTypeElement});
+
+ if (++i === this._sectionLimit && sorted.size > this._sectionLimit) {
+ this._insertTableRow(this._statisticsTable, this._statisticsRows, {labelValue: createEllipsisElement()});
+ break;
+ }
+ }
+ }
+ }
+
+ _layoutSourcesSection()
+ {
+ let statistics = this._statisticsData;
+
+ this._clearSources();
+
+ const unknownLocationKey = "unknown";
+
+ function keyForSourceCodeLocation(sourceCodeLocation) {
+ if (!sourceCodeLocation)
+ return unknownLocationKey;
+
+ return sourceCodeLocation.sourceCode.url + ":" + sourceCodeLocation.lineNumber + ":" + sourceCodeLocation.columnNumber;
+ }
+
+ function labelForLocation(key, sourceCodeLocation, functionName) {
+ if (key === unknownLocationKey) {
+ let span = document.createElement("span");
+ span.className = "unknown";
+ span.textContent = WI.UIString("Unknown Location");
+ return span;
+ }
+
+ const options = {
+ nameStyle: WI.SourceCodeLocation.NameStyle.Short,
+ columnStyle: WI.SourceCodeLocation.ColumnStyle.Shown,
+ dontFloat: true,
+ ignoreNetworkTab: true,
+ ignoreSearchTab: true,
+ };
+ return WI.createSourceCodeLocationLink(sourceCodeLocation, options);
+ }
+
+ let timerFilters = this._sourcesFilter.timer;
+ let eventFilters = this._sourcesFilter.event;
+ let observerFilters = this._sourcesFilter.observer;
+ let hasFilters = (timerFilters.size || eventFilters.size || observerFilters.size);
+
+ let sectionLimit = this._sectionLimit;
+ if (isFinite(sectionLimit) && hasFilters)
+ sectionLimit = CPUTimelineView.defaultSectionLimit * 2;
+
+ let expandAllSections = () => {
+ this._sectionLimit = Infinity;
+ this._layoutStatisticsAndSources();
+ };
+
+ function createEllipsisElement() {
+ let span = document.createElement("span");
+ span.className = "show-more";
+ span.role = "button";
+ span.textContent = ellipsis;
+ span.addEventListener("click", (event) => {
+ expandAllSections();
+ });
+ return span;
+ }
+
+ let timerMap = new Map;
+ let eventHandlerMap = new Map;
+ let observerCallbackMap = new Map;
+ let seenTimers = new Set;
+
+ if (!hasFilters || timerFilters.size) {
+ // Aggregate timers on the location where the timers were installed.
+ // For repeating timers, this includes the total counts the interval fired in the selected time range.
+ for (let record of statistics.timerInstallationRecords) {
+ if (timerFilters.size) {
+ if (record.eventType === WI.ScriptTimelineRecord.EventType.AnimationFrameRequested && !timerFilters.has("requestAnimationFrame"))
+ continue;
+ if (record.eventType === WI.ScriptTimelineRecord.EventType.TimerInstalled && !timerFilters.has("setTimeout"))
+ continue;
+ }
+
+ let callFrame = record.initiatorCallFrame;
+ let sourceCodeLocation = callFrame ? callFrame.sourceCodeLocation : record.sourceCodeLocation;
+ let functionName = callFrame ? callFrame.functionName : "";
+ let key = keyForSourceCodeLocation(sourceCodeLocation);
+ let entry = timerMap.getOrInitialize(key, {sourceCodeLocation, functionName, count: 0, repeating: false});
+ if (record.details) {
+ let timerIdentifier = record.details.timerId;
+ let repeatingEntry = statistics.repeatingTimers.get(timerIdentifier);
+ let count = repeatingEntry ? repeatingEntry.count : 1;
+ entry.count += count;
+ if (record.details.repeating)
+ entry.repeating = true;
+ seenTimers.add(timerIdentifier);
+ } else
+ entry.count += 1;
+ }
+
+ // Aggregate repeating timers where we did not see the installation in the selected time range.
+ // This will use the source code location of where the timer fired, which is better than nothing.
+ if (!hasFilters || timerFilters.has("setTimeout")) {
+ for (let [timerId, repeatingEntry] of statistics.repeatingTimers) {
+ if (seenTimers.has(timerId))
+ continue;
+ // FIXME: <https://webkit.org/b/195351> Web Inspector: CPU Usage Timeline - better resolution of installation source for repeated timers
+ // We could have a map of all repeating timer installations in the whole recording
+ // so that we can provide a function name for these repeating timers lacking an installation point.
+ let sourceCodeLocation = repeatingEntry.record.sourceCodeLocation;
+ let key = keyForSourceCodeLocation(sourceCodeLocation);
+ let entry = timerMap.getOrInitialize(key, {sourceCodeLocation, count: 0, repeating: false});
+ entry.count += repeatingEntry.count;
+ entry.repeating = true;
+ }
+ }
+ }
+
+ if (!hasFilters || eventFilters.size) {
+ for (let record of statistics.eventHandlerRecords) {
+ if (eventFilters.size && !eventFilters.has(record.details))
+ continue;
+ let sourceCodeLocation = record.sourceCodeLocation;
+ let key = keyForSourceCodeLocation(sourceCodeLocation);
+ let entry = eventHandlerMap.getOrInitialize(key, {sourceCodeLocation, count: 0});
+ entry.count += 1;
+ }
+ }
+
+ if (!hasFilters || observerFilters.size) {
+ for (let record of statistics.observerCallbackRecords) {
+ if (observerFilters.size && !observerFilters.has(record.details))
+ continue;
+ let sourceCodeLocation = record.sourceCodeLocation;
+ let key = keyForSourceCodeLocation(record.sourceCodeLocation);
+ let entry = observerCallbackMap.getOrInitialize(key, {sourceCodeLocation, count: 0});
+ entry.count += 1;
+ }
+ }
+
+ const headerValue = "";
+
+ // Sort a Map of key => {count} objects in descending order.
+ function sortMapByEntryCountProperty(map) {
+ let entries = Array.from(map);
+ entries.sort((entryA, entryB) => entryB[1].count - entryA[1].count);
+ return new Map(entries);
+ }
+
+ if (timerMap.size) {
+ let i = 0;
+ let sorted = sortMapByEntryCountProperty(timerMap);
+ for (let [key, entry] of sorted) {
+ let numberValue = entry.repeating ? WI.UIString("~%s", "Approximate Number", "Approximate count of events").format(entry.count) : entry.count;
+ let sourceCodeLocation = entry.callFrame ? entry.callFrame.sourceCodeLocation : entry.sourceCodeLocation;
+ let labelValue = labelForLocation(key, sourceCodeLocation);
+ let followingRow = this._eventHandlersRow;
+
+ let row;
+ if (i === 0) {
+ row = this._timerInstallationsRow;
+ this._timerInstallationsNumberElement.textContent = numberValue;
+ this._timerInstallationsLabelElement.append(labelValue);
+ } else
+ row = this._insertTableRow(this._sourcesTable, this._sourcesRows, {headerValue, numberValue, labelValue, followingRow});
+
+ if (entry.functionName)
+ row.querySelector(".label").append(` ${enDash} ${entry.functionName}`);
+
+ if (++i === sectionLimit && sorted.size > sectionLimit) {
+ this._insertTableRow(this._sourcesTable, this._sourcesRows, {labelValue: createEllipsisElement(), followingRow});
+ break;
+ }
+ }
+ }
+
+ if (eventHandlerMap.size) {
+ let i = 0;
+ let sorted = sortMapByEntryCountProperty(eventHandlerMap);
+ for (let [key, entry] of sorted) {
+ let numberValue = entry.count;
+ let labelValue = labelForLocation(key, entry.sourceCodeLocation);
+ let followingRow = this._observerHandlersRow;
+
+ if (i === 0) {
+ this._eventHandlersNumberElement.textContent = numberValue;
+ this._eventHandlersLabelElement.append(labelValue);
+ } else
+ this._insertTableRow(this._sourcesTable, this._sourcesRows, {headerValue, numberValue, labelValue, followingRow});
+
+ if (++i === sectionLimit && sorted.size > sectionLimit) {
+ this._insertTableRow(this._sourcesTable, this._sourcesRows, {labelValue: createEllipsisElement(), followingRow});
+ break;
+ }
+ }
+ }
+
+ if (observerCallbackMap.size) {
+ let i = 0;
+ let sorted = sortMapByEntryCountProperty(observerCallbackMap);
+ for (let [key, entry] of sorted) {
+ let numberValue = entry.count;
+ let labelValue = labelForLocation(key, entry.sourceCodeLocation);
+
+ if (i === 0) {
+ this._observerHandlersNumberElement.textContent = numberValue;
+ this._observerHandlersLabelElement.append(labelValue);
+ } else
+ this._insertTableRow(this._sourcesTable, this._sourcesRows, {headerValue, numberValue, labelValue});
+
+ if (++i === sectionLimit && sorted.size > sectionLimit) {
+ this._insertTableRow(this._sourcesTable, this._sourcesRows, {labelValue: createEllipsisElement()});
+ break;
+ }
+ }
+ }
+ }
+
_layoutEnergyChart(average, visibleDuration)
{
// The lower the bias value [0..1], the more it increases the skew towards rangeHigh.
@@ -696,7 +1104,7 @@
}
}
- _computeSamplingData(startTime, endTime)
+ _computeStatisticsData(startTime, endTime)
{
// Compute per-millisecond samples of what the main thread was doing.
// We construct an array for every millisecond between the start and end time
@@ -717,23 +1125,78 @@
const includeRecordBeforeStart = true;
+ function incrementTypeCount(map, key) {
+ let entry = map.get(key);
+ if (entry)
+ map.set(key, entry + 1);
+ else
+ map.set(key, 1);
+ }
+
+ let timerInstallationRecords = [];
+ let eventHandlerRecords = [];
+ let observerCallbackRecords = [];
+ let scriptEntries = 0;
+ let timerTypes = new Map;
+ let eventTypes = new Map;
+ let observerTypes = new Map;
+
+ let repeatingTimers = new Map;
+ let possibleRepeatingTimers = new Set;
+
let scriptTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Script);
let scriptRecords = scriptTimeline ? scriptTimeline.recordsInTimeRange(startTime, endTime, includeRecordBeforeStart) : [];
scriptRecords = scriptRecords.filter((record) => {
+ // Return true for event types that define script entries/exits.
+ // Return false for events with no time ranges or if they are contained in other events.
switch (record.eventType) {
case WI.ScriptTimelineRecord.EventType.ScriptEvaluated:
case WI.ScriptTimelineRecord.EventType.APIScriptEvaluated:
+ scriptEntries++;
+ return true;
+
case WI.ScriptTimelineRecord.EventType.ObserverCallback:
+ incrementTypeCount(observerTypes, record.details);
+ observerCallbackRecords.push(record);
+ scriptEntries++;
+ return true;
+
case WI.ScriptTimelineRecord.EventType.EventDispatched:
+ incrementTypeCount(eventTypes, record.details);
+ eventHandlerRecords.push(record);
+ scriptEntries++;
+ return true;
+
case WI.ScriptTimelineRecord.EventType.MicrotaskDispatched:
+ // Do not normally count this as a script entry, but they may have a time range
+ // that is not covered by script entry (queueMicrotask).
+ return true;
+
case WI.ScriptTimelineRecord.EventType.TimerFired:
+ incrementTypeCount(timerTypes, "setTimeout");
+ if (possibleRepeatingTimers.has(record.details)) {
+ let entry = repeatingTimers.get(record.details);
+ if (entry)
+ entry.count += 1;
+ else
+ repeatingTimers.set(record.details, {record, count: 1});
+ } else
+ possibleRepeatingTimers.add(record.details);
+ scriptEntries++;
+ return true;
+
case WI.ScriptTimelineRecord.EventType.AnimationFrameFired:
- // These event types define script entry/exits.
+ incrementTypeCount(timerTypes, "requestAnimationFrame");
+ scriptEntries++;
return true;
case WI.ScriptTimelineRecord.EventType.AnimationFrameRequested:
+ case WI.ScriptTimelineRecord.EventType.TimerInstalled:
+ // These event types have no time range, or are contained by the others.
+ timerInstallationRecords.push(record);
+ return false;
+
case WI.ScriptTimelineRecord.EventType.AnimationFrameCanceled:
- case WI.ScriptTimelineRecord.EventType.TimerInstalled:
case WI.ScriptTimelineRecord.EventType.TimerRemoved:
case WI.ScriptTimelineRecord.EventType.ProbeSampleRecorded:
case WI.ScriptTimelineRecord.EventType.ConsoleProfileRecorded:
@@ -844,6 +1307,14 @@
samplesLayout,
samplesPaint,
samplesStyle,
+ scriptEntries,
+ timerTypes,
+ eventTypes,
+ observerTypes,
+ timerInstallationRecords,
+ eventHandlerRecords,
+ observerCallbackRecords,
+ repeatingTimers,
};
}
@@ -858,6 +1329,132 @@
this._workerViews = [];
}
+ _resetSourcesFilters()
+ {
+ if (!this._sourcesFilter)
+ return;
+
+ this._sourcesFilterRow.hidden = true;
+ this._sourcesFilterLabelElement.removeChildren();
+
+ this._timerInstallationsRow.hidden = false;
+ this._eventHandlersRow.hidden = false;
+ this._observerHandlersRow.hidden = false;
+
+ this._sourcesFilter.timer.clear();
+ this._sourcesFilter.event.clear();
+ this._sourcesFilter.observer.clear();
+ }
+
+ _addSourcesFilter(type, name)
+ {
+ this._sourcesFilter[type].add(name);
+ this._updateSourcesFilters();
+ }
+
+ _removeSourcesFilter(type, name)
+ {
+ this._sourcesFilter[type].delete(name);
+ this._updateSourcesFilters();
+ }
+
+ _updateSourcesFilters()
+ {
+ let timerFilters = this._sourcesFilter.timer;
+ let eventFilters = this._sourcesFilter.event;
+ let observerFilters = this._sourcesFilter.observer;
+
+ if (!timerFilters.size && !eventFilters.size && !observerFilters.size) {
+ this._resetSourcesFilters();
+ return;
+ }
+
+ let createActiveFilterElement = (type, name) => {
+ let span = document.createElement("span");
+ span.className = "filter active";
+ span.textContent = name;
+ span.addEventListener("mouseup", (event) => {
+ this._removeSourcesFilter(type, name);
+ this._layoutStatisticsAndSources();
+ });
+ return span;
+ }
+
+ this._sourcesFilterRow.hidden = false;
+ this._sourcesFilterLabelElement.removeChildren();
+
+ for (let name of timerFilters)
+ this._sourcesFilterLabelElement.appendChild(createActiveFilterElement("timer", name));
+ for (let name of eventFilters)
+ this._sourcesFilterLabelElement.appendChild(createActiveFilterElement("event", name));
+ for (let name of observerFilters)
+ this._sourcesFilterLabelElement.appendChild(createActiveFilterElement("observer", name));
+
+ this._timerInstallationsRow.hidden = !timerFilters.size;
+ this._eventHandlersRow.hidden = !eventFilters.size;
+ this._observerHandlersRow.hidden = !observerFilters.size;
+ }
+
+ _createTableRow(table)
+ {
+ let row = table.appendChild(document.createElement("tr"));
+
+ let headerCell = row.appendChild(document.createElement("th"));
+
+ let numberCell = row.appendChild(document.createElement("td"));
+ numberCell.className = "number";
+
+ let labelCell = row.appendChild(document.createElement("td"));
+ labelCell.className = "label";
+
+ return {row, headerCell, numberCell, labelCell};
+ }
+
+ _insertTableRow(table, rowList, {headerValue, numberValue, labelValue, followingRow})
+ {
+ let {row, headerCell, numberCell, labelCell} = this._createTableRow(table);
+ rowList.push(row);
+
+ if (followingRow)
+ table.insertBefore(row, followingRow);
+
+ if (headerValue)
+ headerCell.textContent = headerValue;
+
+ if (numberValue)
+ numberCell.textContent = numberValue;
+
+ if (labelValue)
+ labelCell.append(labelValue);
+
+ return row;
+ }
+
+ _clearStatistics()
+ {
+ this._scriptEntriesNumberElement.textContent = emDash;
+
+ for (let row of this._statisticsRows)
+ row.remove();
+ this._statisticsRows = [];
+ }
+
+ _clearSources()
+ {
+ this._timerInstallationsNumberElement.textContent = emDash;
+ this._timerInstallationsLabelElement.textContent = "";
+
+ this._eventHandlersNumberElement.textContent = emDash;
+ this._eventHandlersLabelElement.textContent = "";
+
+ this._observerHandlersNumberElement.textContent = emDash;
+ this._observerHandlersLabelElement.textContent = "";
+
+ for (let row of this._sourcesRows)
+ row.remove();
+ this._sourcesRows = [];
+ }
+
_clearEnergyImpactText()
{
this._energyImpactLabelElement.classList.remove("low", "medium", "high");
@@ -1009,6 +1606,10 @@
}
};
+WI.CPUTimelineView.LayoutReason = {
+ Internal: Symbol("cpu-timeline-view-internal-layout"),
+};
+
// NOTE: UI follows this order.
WI.CPUTimelineView.SampleType = {
Script: "sample-type-script",
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Main.css (242561 => 242562)
--- trunk/Source/WebInspectorUI/UserInterface/Views/Main.css 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Main.css 2019-03-06 20:38:43 UTC (rev 242562)
@@ -238,7 +238,7 @@
.resource-link,
.go-to-link {
- color: hsl(0, 0%, 33%);
+ color: var(--link-text-color);
text-decoration: underline;
cursor: pointer;
-webkit-user-select: none;
@@ -477,11 +477,6 @@
filter: unset;
}
- .resource-link,
- .go-to-link {
- color: var(--text-color-secondary);
- }
-
.expand-list-button {
color: inherit;
}
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css (242561 => 242562)
--- trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css 2019-03-06 20:18:00 UTC (rev 242561)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css 2019-03-06 20:38:43 UTC (rev 242562)
@@ -50,6 +50,12 @@
--background-color-content: white;
--background-color-code: white;
+ /* Gray background with lighter foreground. In dark mode this is lighter. */
+ --gray-background-color: hsl(0, 0%, 66%);
+ --gray-foreground-color: white;
+
+ --link-text-color: hsl(0, 0%, 33%);
+
--selected-foreground-color: white;
--selected-secondary-text-color: hsla(0, 100%, 100%, 0.7);
--selected-background-color: hsl(212, 92%, 54%);
@@ -215,6 +221,11 @@
--background-color-content: hsl(0, 0%, 21%);
--background-color-code: hsl(0, 0%, 21%);
+ --gray-background-color: hsl(0, 0%, 50%);
+ --gray-foreground-color: hsl(0, 0%, 33%);
+
+ --link-text-color: var(--text-color-secondary);
+
--background-color-alternate: hsla(0, 0%, var(--foreground-lightness), 0.05);
--background-color-selected: hsla(0, 0%, var(--foreground-lightness), 0.1);