WebKit Bugzilla
Attachment 362802 Details for
Bug 194972
: Web Inspector: CPU Usage Timeline - Main Thread Indicator
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
[PATCH] Proposed Fix
indicator-1.patch (text/plain), 30.43 KB, created by
Joseph Pecoraro
on 2019-02-22 17:29:33 PST
(
hide
)
Description:
[PATCH] Proposed Fix
Filename:
MIME Type:
Creator:
Joseph Pecoraro
Created:
2019-02-22 17:29:33 PST
Size:
30.43 KB
patch
obsolete
>diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog >index 0e9ac626484..843d658cbe3 100644 >--- a/Source/WebInspectorUI/ChangeLog >+++ b/Source/WebInspectorUI/ChangeLog >@@ -1,3 +1,77 @@ >+2019-02-22 Joseph Pecoraro <pecoraro@apple.com> >+ >+ Web Inspector: CPU Usage Timeline - Main Thread Indicator >+ https://bugs.webkit.org/show_bug.cgi?id=194972 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * UserInterface/Main.html: >+ * UserInterface/Base/Utilities.js: >+ (value): >+ The existing enclosingNode doesn't work for SVG because its names >+ are lowercase. Add a simplified version for the svg case. >+ >+ * UserInterface/Views/RangeChart.js: Added. >+ (WI.RangeChart): >+ (WI.RangeChart.prototype.get size): >+ (WI.RangeChart.prototype.set size): >+ (WI.RangeChart.prototype.addRange): >+ (WI.RangeChart.prototype.clear): >+ (WI.RangeChart.prototype.layout): >+ A new chart that draws rects for given ranges. >+ >+ * UserInterface/Models/Timeline.js: >+ (WI.Timeline.prototype.recordsOverlappingTimeRange): >+ Helper to specifically get records touching a range. Useful >+ for when we have a single pixel spanning (startTime -> endTime) >+ and we want to find records in that pixel. >+ >+ * UserInterface/Views/CPUTimelineView.css: >+ (.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart rect): >+ (.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-script): >+ (.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-style): >+ (.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-layout): >+ (.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-paint): >+ * UserInterface/Views/CPUTimelineView.js: >+ (WI.CPUTimelineView.prototype.get indicatorViewHeight): >+ (WI.CPUTimelineView.prototype.clear): >+ (WI.CPUTimelineView.prototype.get scrollableElements): >+ (WI.CPUTimelineView.prototype.initialLayout): >+ (WI.CPUTimelineView.prototype.layout): >+ (WI.CPUTimelineView.prototype._graphPositionForMouseEvent): >+ (WI.CPUTimelineView.prototype._handleIndicatorClick): >+ (WI.CPUTimelineView.prototype._attemptSelectIndicatatorTimelineRecord): >+ (WI.CPUTimelineView.prototype._selectTimelineRecord): >+ Place the Main Thread Indicator view beneath the big graph. >+ Clicks inside it select records in the Timeline Overview. >+ >+ * UserInterface/Views/CPUUsageIndicatorView.css: Added. >+ (.cpu-usage-indicator-view): >+ (.cpu-usage-indicator-view > .details): >+ (body[dir=ltr] .cpu-usage-indicator-view > .details): >+ (body[dir=rtl] .cpu-usage-indicator-view > .details): >+ (body[dir=rtl] .cpu-usage-indicator-view > .graph): >+ (.cpu-usage-indicator-view > .graph): >+ (.cpu-usage-indicator-view > .graph,): >+ * UserInterface/Views/CPUUsageIndicatorView.js: Added. >+ (WI.CPUUsageIndicatorView): >+ (WI.CPUUsageIndicatorView.prototype.get chart): >+ (WI.CPUUsageIndicatorView.prototype.clear): >+ (WI.CPUUsageIndicatorView.prototype.updateChart): >+ Converts the CPU samples data into a RangeChart. It works to coalesce >+ many samples of the same type into a single range to reduce total ranges. >+ >+ * UserInterface/Views/TimelineRecordingContentView.js: >+ (WI.TimelineRecordingContentView): >+ (WI.TimelineRecordingContentView.prototype._recordSelected): >+ (WI.TimelineRecordingContentView.prototype._recordWasSelected): >+ (WI.TimelineRecordingContentView.prototype._selectRecordInTimelineOverview): >+ (WI.TimelineRecordingContentView.prototype._selectRecordInTimelineView): >+ * UserInterface/Views/TimelineView.js: >+ Add a path for a TimelineView to dispatch a record selected event and cause >+ have the TimelineRecordingContentView react to it by updating the timeline >+ overview and relevent timeline view. >+ > 2019-02-18 Joseph Pecoraro <pecoraro@apple.com> > > Web Inspector: CPU Usage Timeline - Thread Breakdown >diff --git a/Source/WebInspectorUI/UserInterface/Base/Utilities.js b/Source/WebInspectorUI/UserInterface/Base/Utilities.js >index 4f28dccdb38..2855066efa5 100644 >--- a/Source/WebInspectorUI/UserInterface/Base/Utilities.js >+++ b/Source/WebInspectorUI/UserInterface/Base/Utilities.js >@@ -218,6 +218,19 @@ Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeName", > } > }); > >+Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithLocalName", >+{ >+ value(localName) >+ { >+ for (let node = this; node; node = node.parentElement) { >+ if (node.localName === localName) >+ return node; >+ } >+ >+ return null; >+ } >+}); >+ > Object.defineProperty(Node.prototype, "traverseNextNode", > { > value(stayWithin) >diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html >index 5f96b02273e..3fbcd6b12c0 100644 >--- a/Source/WebInspectorUI/UserInterface/Main.html >+++ b/Source/WebInspectorUI/UserInterface/Main.html >@@ -45,6 +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/CPUUsageView.css"> > <link rel="stylesheet" href="Views/CallFrameIcons.css"> >@@ -588,6 +589,7 @@ > <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/CPUUsageView.js"></script> > <script src="Views/CSSStyleSheetTreeElement.js"></script> >@@ -742,6 +744,7 @@ > <script src="Views/QuickConsole.js"></script> > <script src="Views/QuickConsoleNavigationBar.js"></script> > <script src="Views/RadioButtonNavigationItem.js"></script> >+ <script src="Views/RangeChart.js"></script> > <script src="Views/RecordingActionTreeElement.js"></script> > <script src="Views/RecordingContentView.js"></script> > <script src="Views/RecordingStateDetailsSidebarPanel.js"></script> >diff --git a/Source/WebInspectorUI/UserInterface/Models/Timeline.js b/Source/WebInspectorUI/UserInterface/Models/Timeline.js >index 756ce574c40..82949ccf7fb 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/Timeline.js >+++ b/Source/WebInspectorUI/UserInterface/Models/Timeline.js >@@ -93,6 +93,14 @@ WI.Timeline = class Timeline extends WI.Object > this.dispatchEventToListeners(WI.Timeline.Event.Refreshed); > } > >+ recordsOverlappingTimeRange(startTime, endTime) >+ { >+ let lowerIndex = this._records.lowerBound(startTime, (time, record) => time - record.endTime); >+ let upperIndex = this._records.upperBound(endTime, (time, record) => time - record.startTime); >+ >+ return this._records.slice(lowerIndex, upperIndex); >+ } >+ > recordsInTimeRange(startTime, endTime, includeRecordBeforeStart) > { > let lowerIndex = this._records.lowerBound(startTime, (time, record) => time - record.startTime); >diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css >index 18952adc324..c2116e7f0dc 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css >@@ -215,3 +215,27 @@ body[dir=rtl] .timeline-view.cpu :matches(.line-chart, .stacked-line-chart) .mar > color: var(--text-color-secondary); > background-color: var(--background-color-content); > } >+ >+.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart rect { >+ stroke-opacity: 0.25; >+} >+ >+.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 .cpu-usage-indicator-view > .graph > .range-chart .sample-type-paint { >+ stroke: var(--cpu-paint-stroke-color); >+ fill: var(--cpu-paint-fill-color); >+} >diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js >index d333dc040f6..d851269e2a5 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js >@@ -56,6 +56,7 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > > static get cpuUsageViewHeight() { return 150; } > static get threadCPUUsageViewHeight() { return 65; } >+ static get indicatorViewHeight() { return 15; } > > // Public > >@@ -103,21 +104,24 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > clearUsageView(this._unknownThreadUsageView); > > this._removeWorkerThreadViews(); >- } > >- get scrollableElements() >- { >- return [this.element]; >+ this._mainThreadWorkIndicatorView.clear(); > } > > // Protected > > get showsFilterBar() { return false; } > >+ get scrollableElements() >+ { >+ return [this.element]; >+ } >+ > initialLayout() > { > this.element.style.setProperty("--cpu-usage-stacked-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"); > > let contentElement = this.element.appendChild(document.createElement("div")); > contentElement.classList.add("content"); >@@ -194,6 +198,12 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > this.addSubview(this._cpuUsageView); > this._detailsContainerElement.appendChild(this._cpuUsageView.element); > >+ this._mainThreadWorkIndicatorView = new WI.CPUUsageIndicatorView; >+ this.addSubview(this._mainThreadWorkIndicatorView); >+ this._detailsContainerElement.appendChild(this._mainThreadWorkIndicatorView.element); >+ >+ this._mainThreadWorkIndicatorView.chart.element.addEventListener("click", this._handleIndicatorClick.bind(this)); >+ > let threadsSubtitleElement = detailsContainerElement.appendChild(document.createElement("div")); > threadsSubtitleElement.classList.add("subtitle", "threads"); > threadsSubtitleElement.textContent = WI.UIString("Threads"); >@@ -495,6 +505,14 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > > layoutView(workerView, "usage", CPUTimelineView.threadCPUUsageViewHeight, {dataPoints: workerData.dataPoints, layoutMax, min: workerData.min, max: workerData.max, average: workerData.average}); > } >+ >+ function xScaleIndicatorRange(sampleIndex) { >+ return (sampleIndex / 1000) / secondsPerPixel; >+ } >+ >+ let graphWidth = (graphEndTime - graphStartTime) / secondsPerPixel; >+ let size = new WI.Size(graphWidth, CPUTimelineView.indicatorViewHeight); >+ this._mainThreadWorkIndicatorView.updateChart(samplingData.samples, size, visibleEndTime, xScaleIndicatorRange); > } > > // Private >@@ -649,6 +667,109 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > if (cpuTimelineRecord.startTime >= this.startTime && cpuTimelineRecord.endTime <= this.endTime) > this.needsLayout(); > } >+ >+ _graphPositionForMouseEvent(event) >+ { >+ let svgElement = event.target.enclosingNodeOrSelfWithLocalName("svg"); >+ if (!svgElement) >+ return NaN; >+ >+ let svgRect = svgElement.getBoundingClientRect(); >+ let position = event.pageX - svgRect.left; >+ >+ if (WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL) >+ return svgRect.width - position; >+ return position; >+ } >+ >+ _handleIndicatorClick(event) >+ { >+ let clickPosition = this._graphPositionForMouseEvent(event); >+ if (isNaN(clickPosition)) >+ return; >+ >+ let secondsPerPixel = this._timelineRuler.secondsPerPixel; >+ let graphClickTime = clickPosition * secondsPerPixel; >+ let graphStartTime = this.startTime; >+ >+ let clickStartTime = graphStartTime + graphClickTime; >+ let clickEndTime = clickStartTime + secondsPerPixel; >+ >+ // Try at the exact clicked pixel. >+ if (event.target.localName === "rect") { >+ if (this._attemptSelectIndicatatorTimelineRecord(clickStartTime, clickEndTime)) >+ return; >+ console.assert(false, "If the user clicked on a rect there should have been a record in this pixel range"); >+ } >+ >+ // Spiral out 4 pixels each side to try and select a nearby record. >+ for (let i = 1, delta = 0; i <= 4; ++i) { >+ delta += secondsPerPixel; >+ if (this._attemptSelectIndicatatorTimelineRecord(clickStartTime - delta, clickEndTime)) >+ return; >+ if (this._attemptSelectIndicatatorTimelineRecord(clickStartTime, clickStartTime + delta)) >+ return; >+ } >+ } >+ >+ _attemptSelectIndicatatorTimelineRecord(startTime, endTime) >+ { >+ let layoutTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Layout); >+ let layoutRecords = layoutTimeline ? layoutTimeline.recordsOverlappingTimeRange(startTime, endTime) : []; >+ layoutRecords = layoutRecords.filter((record) => { >+ switch (record.eventType) { >+ case WI.LayoutTimelineRecord.EventType.RecalculateStyles: >+ case WI.LayoutTimelineRecord.EventType.ForcedLayout: >+ case WI.LayoutTimelineRecord.EventType.Layout: >+ case WI.LayoutTimelineRecord.EventType.Paint: >+ case WI.LayoutTimelineRecord.EventType.Composite: >+ return true; >+ case WI.LayoutTimelineRecord.EventType.InvalidateStyles: >+ case WI.LayoutTimelineRecord.EventType.InvalidateLayout: >+ return false >+ } >+ }); >+ >+ if (layoutRecords.length) { >+ this._selectTimelineRecord(layoutRecords[0]); >+ return true; >+ } >+ >+ let scriptTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Script); >+ let scriptRecords = scriptTimeline ? scriptTimeline.recordsOverlappingTimeRange(startTime, endTime) : []; >+ scriptRecords = scriptRecords.filter((record) => { >+ switch (record.eventType) { >+ case WI.ScriptTimelineRecord.EventType.ScriptEvaluated: >+ case WI.ScriptTimelineRecord.EventType.APIScriptEvaluated: >+ case WI.ScriptTimelineRecord.EventType.ObserverCallback: >+ case WI.ScriptTimelineRecord.EventType.EventDispatched: >+ case WI.ScriptTimelineRecord.EventType.MicrotaskDispatched: >+ case WI.ScriptTimelineRecord.EventType.TimerFired: >+ case WI.ScriptTimelineRecord.EventType.AnimationFrameFired: >+ return true; >+ case WI.ScriptTimelineRecord.EventType.AnimationFrameRequested: >+ 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: >+ case WI.ScriptTimelineRecord.EventType.GarbageCollected: >+ return false; >+ } >+ }); >+ >+ if (scriptRecords.length) { >+ this._selectTimelineRecord(scriptRecords[0]); >+ return true; >+ } >+ >+ return false; >+ } >+ >+ _selectTimelineRecord(record) >+ { >+ this.dispatchEventToListeners(WI.TimelineView.Event.RecordWasSelected, {record}); >+ } > }; > > WI.CPUTimelineView.SampleType = { >diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUUsageIndicatorView.css b/Source/WebInspectorUI/UserInterface/Views/CPUUsageIndicatorView.css >new file mode 100644 >index 00000000000..b723885432b >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUUsageIndicatorView.css >@@ -0,0 +1,63 @@ >+/* >+ * 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-view-details-border-end: 1px solid var(--border-color); >+} >+ >+body[dir=ltr] .cpu-usage-indicator-view > .details { >+ border-right: var(--cpu-usage-view-details-border-end); >+} >+ >+body[dir=rtl] .cpu-usage-indicator-view > .details { >+ border-left: var(--cpu-usage-view-details-border-end); >+} >+ >+body[dir=rtl] .cpu-usage-indicator-view > .graph { >+ transform: scaleX(-1); >+} >+ >+.cpu-usage-indicator-view > .graph { >+ position: relative; >+ background-color: var(--background-color-content); >+ z-index: calc(var(--timeline-marker-z-index) + 1); >+} >+ >+.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 >new file mode 100644 >index 00000000000..bd90f274eed >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUUsageIndicatorView.js >@@ -0,0 +1,109 @@ >+/* >+ * 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 changed type, 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/RangeChart.js b/Source/WebInspectorUI/UserInterface/Views/RangeChart.js >new file mode 100644 >index 00000000000..bc87bd327ff >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Views/RangeChart.js >@@ -0,0 +1,110 @@ >+/* >+ * 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. >+ */ >+ >+// RangeChart creates a chart filled with ranges of equal height. >+// >+// [ |------|------| |------| ] >+// >+// Initialize the chart with a size. You can then include a new range >+// in the chart by providing an (x, w, class) via `addRange`. >+// >+// SVG: >+// >+// - There is a single rect for each range. >+// >+// <div class="range-chart"> >+// <svg viewBox="0 0 800 75"> >+// <rect width="<w>" height="100%" transform="translateX(<x>)" class="<class>" /> >+// <rect width="<w>" height="100%" transform="translateX(<x>)" class="<class>" /> >+// ... >+// </svg> >+// </div> >+ >+WI.RangeChart = class RangeChart extends WI.View >+{ >+ constructor() >+ { >+ super(); >+ >+ this.element.classList.add("range-chart"); >+ >+ this._svgElement = this.element.appendChild(createSVGElement("svg")); >+ this._svgElement.setAttribute("preserveAspectRatio", "none"); >+ >+ this._ranges = []; >+ this._size = null; >+ } >+ >+ // Public >+ >+ get size() >+ { >+ return this._size; >+ } >+ >+ set size(size) >+ { >+ if (this._size && this._size.equals(size)) >+ return; >+ >+ this._size = size; >+ >+ this._svgElement.setAttribute("viewBox", `0 0 ${size.width} ${size.height}`); >+ } >+ >+ addRange(x, width, className) >+ { >+ this._ranges.push({x, width, className}); >+ } >+ >+ clear() >+ { >+ this._ranges = []; >+ } >+ >+ // Protected >+ >+ layout() >+ { >+ super.layout(); >+ >+ if (this.layoutReason === WI.View.LayoutReason.Resize) >+ return; >+ >+ if (!this._size) >+ return; >+ >+ this._svgElement.removeChildren(); >+ >+ let h = 0; >+ for (let {x, width, className} of this._ranges) { >+ let rect = this._svgElement.appendChild(createSVGElement("rect")); >+ rect.setAttribute("width", width); >+ rect.setAttribute("height", this.size.height); >+ rect.setAttribute("transform", `translate(${x}, 0)`); >+ rect.classList.add(className); >+ } >+ } >+}; >diff --git a/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js b/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js >index a6d1066a933..45c44ac0c4f 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js >@@ -91,6 +91,7 @@ WI.TimelineRecordingContentView = class TimelineRecordingContentView extends WI. > WI.ContentView.addEventListener(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange, this._contentViewSupplementalRepresentedObjectsDidChange, this); > > WI.TimelineView.addEventListener(WI.TimelineView.Event.RecordWasFiltered, this._recordWasFiltered, this); >+ WI.TimelineView.addEventListener(WI.TimelineView.Event.RecordWasSelected, this._recordWasSelected, this); > > WI.notifications.addEventListener(WI.Notification.VisibilityStateDidChange, this._inspectorVisibilityStateChanged, this); > >@@ -680,15 +681,7 @@ WI.TimelineRecordingContentView = class TimelineRecordingContentView extends WI. > { > let {record} = event.data; > >- for (let timelineView of this._timelineViewMap.values()) { >- let recordMatchesTimeline = record && timelineView.representedObject.type === record.type; >- >- if (recordMatchesTimeline && timelineView !== this.currentTimelineView) >- this.showTimelineViewForTimeline(timelineView.representedObject); >- >- if (!record || recordMatchesTimeline) >- timelineView.selectRecord(record); >- } >+ this._selectRecordInTimelineView(record); > } > > _timelineSelected() >@@ -800,6 +793,39 @@ WI.TimelineRecordingContentView = class TimelineRecordingContentView extends WI. > this._timelineOverview.recordWasFiltered(timeline, record, filtered); > } > >+ _recordWasSelected(event) >+ { >+ if (!this.visible) >+ return; >+ >+ let {record} = event.data; >+ >+ this._selectRecordInTimelineOverview(record); >+ this._selectRecordInTimelineView(record); >+ } >+ >+ _selectRecordInTimelineOverview(record) >+ { >+ let timeline = this._recording.timelineForRecordType(record.type); >+ if (!timeline) >+ return; >+ >+ this._timelineOverview.selectRecord(timeline, record); >+ } >+ >+ _selectRecordInTimelineView(record) >+ { >+ for (let timelineView of this._timelineViewMap.values()) { >+ let recordMatchesTimeline = record && timelineView.representedObject.type === record.type; >+ >+ if (recordMatchesTimeline && timelineView !== this.currentTimelineView) >+ this.showTimelineViewForTimeline(timelineView.representedObject); >+ >+ if (!record || recordMatchesTimeline) >+ timelineView.selectRecord(record); >+ } >+ } >+ > _updateProgressView() > { > let isCapturing = WI.timelineManager.isCapturing(); >diff --git a/Source/WebInspectorUI/UserInterface/Views/TimelineView.js b/Source/WebInspectorUI/UserInterface/Views/TimelineView.js >index 23842f2c085..d79d5e8294c 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/TimelineView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/TimelineView.js >@@ -335,5 +335,6 @@ WI.TimelineView = class TimelineView extends WI.ContentView > }; > > WI.TimelineView.Event = { >- RecordWasFiltered: "record-was-filtered" >+ RecordWasFiltered: "record-was-filtered", >+ RecordWasSelected: "record-was-selected", > };
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 194972
:
362802
|
362960
|
362961
|
362964