Diff
Modified: trunk/Source/WebInspectorUI/ChangeLog (242566 => 242567)
--- trunk/Source/WebInspectorUI/ChangeLog 2019-03-06 22:28:27 UTC (rev 242566)
+++ trunk/Source/WebInspectorUI/ChangeLog 2019-03-06 22:28:30 UTC (rev 242567)
@@ -1,5 +1,51 @@
2019-03-06 Joseph Pecoraro <pecor...@apple.com>
+ Web Inspector: CPU Usage Timeline - Allow clicking a bar in the overview to select a tight time range around it
+ https://bugs.webkit.org/show_bug.cgi?id=195321
+
+ Reviewed by Devin Rousso.
+
+ * UserInterface/Models/Timeline.js:
+ (WI.Timeline.prototype.closestRecordTo):
+ Helper to get the closest record to a timestamp.
+
+ * UserInterface/Views/CPUTimelineOverviewGraph.css:
+ (.timeline-overview-graph.cpu > .stacked-column-chart):
+ (.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.selected):
+ Style a selected record with the active color.
+
+ * UserInterface/Views/CPUTimelineOverviewGraph.js:
+ (WI.CPUTimelineOverviewGraph):
+ (WI.CPUTimelineOverviewGraph.prototype.get samplingRatePerSecond):
+ (WI.CPUTimelineOverviewGraph.prototype.reset):
+ (WI.CPUTimelineOverviewGraph.prototype.layout):
+ (WI.CPUTimelineOverviewGraph.prototype.updateSelectedRecord):
+ (WI.CPUTimelineOverviewGraph.prototype._graphPositionForMouseEvent):
+ (WI.CPUTimelineOverviewGraph.prototype._handleGraphMouseClick):
+ A click in the overview which hits a rect triggers a selection of
+ the associated timeline record.
+
+ * UserInterface/Views/StackedColumnChart.js:
+ (WI.StackedColumnChart.prototype.addColumnSet):
+ (WI.StackedColumnChart.prototype.layout):
+ Allow setting an additional class name with a column set.
+ It will set the class name on each rect in that column.
+
+ * UserInterface/Views/TimelineOverview.js:
+ (WI.TimelineOverview.prototype._recordSelected):
+ When selecting a CPU record, make a selection range of 2 neighboring
+ columns in each direction.
+
+ * UserInterface/Views/TimelineRuler.js:
+ (WI.TimelineRuler.prototype._handleClick):
+ When a sub-element has handled the click stop further event propagation.
+
+ * UserInterface/Views/TimelineOverviewGraph.js:
+ (WI.TimelineOverviewGraph.prototype.get selected):
+ Drive-by style fix.
+
+2019-03-06 Joseph Pecoraro <pecor...@apple.com>
+
Web Inspector: TimelineOverview clicks do not always behave as expected
https://bugs.webkit.org/show_bug.cgi?id=195319
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Timeline.js (242566 => 242567)
--- trunk/Source/WebInspectorUI/UserInterface/Models/Timeline.js 2019-03-06 22:28:27 UTC (rev 242566)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Timeline.js 2019-03-06 22:28:30 UTC (rev 242567)
@@ -93,6 +93,24 @@
this.dispatchEventToListeners(WI.Timeline.Event.Refreshed);
}
+ closestRecordTo(timestamp)
+ {
+ let lowerIndex = this._records.lowerBound(timestamp, (time, record) => time - record.endTime);
+
+ let recordBefore = this._records[lowerIndex - 1];
+ let recordAfter = this._records[lowerIndex];
+ if (!recordBefore && !recordAfter)
+ return null;
+ if (!recordBefore && recordAfter)
+ return recordAfter;
+ if (!recordAfter && recordBefore)
+ return recordBefore;
+
+ let before = Math.abs(recordBefore.endTime - timestamp);
+ let after = Math.abs(recordAfter.startTime - timestamp);
+ return (before < after) ? recordBefore : recordAfter;
+ }
+
recordsOverlappingTimeRange(startTime, endTime)
{
let lowerIndex = this._records.lowerBound(startTime, (time, record) => time - record.endTime);
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css (242566 => 242567)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css 2019-03-06 22:28:27 UTC (rev 242566)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css 2019-03-06 22:28:30 UTC (rev 242567)
@@ -74,6 +74,13 @@
fill: var(--cpu-worker-thread-fill-color);
}
+.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.selected {
+ fill: var(--selected-background-color) !important;
+ fill-opacity: 0.5;
+ stroke: var(--selected-background-color-active) !important;
+ stroke-opacity: 0.8;
+}
+
/* LegacyCPUTimeline */
.timeline-overview-graph.cpu > .column-chart > svg > rect {
stroke: var(--cpu-stroke-color);
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js (242566 => 242567)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js 2019-03-06 22:28:27 UTC (rev 242566)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js 2019-03-06 22:28:30 UTC (rev 242567)
@@ -46,12 +46,24 @@
this.addSubview(this._chart);
this.element.appendChild(this._chart.element);
+ this._chart.element.addEventListener("click", this._handleChartClick.bind(this));
+
this._legendElement = this.element.appendChild(document.createElement("div"));
this._legendElement.classList.add("legend");
+ this._lastSelectedRecordInLayout = null;
+
this.reset();
}
+ // Static
+
+ static get samplingRatePerSecond()
+ {
+ // 500ms. This matches the ResourceUsageThread sampling frequency in the backend.
+ return 0.5;
+ }
+
// Protected
get height()
@@ -65,6 +77,7 @@
this._maxUsage = 0;
this._cachedMaxUsage = undefined;
+ this._lastSelectedRecordInLayout = null;
this._updateLegend();
this._chart.clear();
@@ -83,6 +96,8 @@
if (isNaN(graphWidth))
return;
+ this._lastSelectedRecordInLayout = this.selectedRecord;
+
if (this._chart.size.width !== graphWidth || this._chart.size.height !== this.height)
this._chart.size = new WI.Size(graphWidth, this.height);
@@ -91,9 +106,6 @@
let secondsPerPixel = this.timelineOverview.secondsPerPixel;
let maxCapacity = Math.max(20, this._maxUsage * 1.05); // Add 5% for padding.
- // 500ms. This matches the ResourceUsageThread sampling frequency in the backend.
- const samplingRatePerSecond = 0.5;
-
function xScale(time) {
return (time - graphStartTime) / secondsPerPixel;
}
@@ -104,7 +116,7 @@
}
const includeRecordBeforeStart = true;
- let visibleRecords = this._cpuTimeline.recordsInTimeRange(graphStartTime, visibleEndTime + (samplingRatePerSecond / 2), includeRecordBeforeStart);
+ let visibleRecords = this._cpuTimeline.recordsInTimeRange(graphStartTime, visibleEndTime + (CPUTimelineOverviewGraph.samplingRatePerSecond / 2), includeRecordBeforeStart);
if (!visibleRecords.length)
return;
@@ -112,7 +124,7 @@
return yScale(record.usage);
}
- let intervalWidth = (samplingRatePerSecond / secondsPerPixel);
+ let intervalWidth = (CPUTimelineOverviewGraph.samplingRatePerSecond / secondsPerPixel);
const minimumDisplayHeight = 4;
// Bars for each record.
@@ -119,16 +131,29 @@
for (let record of visibleRecords) {
let w = intervalWidth;
let h3 = Math.max(minimumDisplayHeight, yScale(record.usage));
- let x = xScale(record.startTime - (samplingRatePerSecond / 2));
+ let x = xScale(record.startTime - (CPUTimelineOverviewGraph.samplingRatePerSecond / 2));
if (WI.settings.experimentalEnableCPUUsageEnhancements.value) {
+ let additionalClass = record === this.selectedRecord ? "selected" : undefined;
let h1 = Math.max(minimumDisplayHeight, yScale(record.mainThreadUsage));
let h2 = Math.max(minimumDisplayHeight, yScale(record.mainThreadUsage + record.workerThreadUsage));
- this._chart.addColumnSet(x, height, w, [h1, h2, h3]);
+ this._chart.addColumnSet(x, height, w, [h1, h2, h3], additionalClass);
} else
this._chart.addColumn(x, height - h3, w, h3);
}
}
+ updateSelectedRecord()
+ {
+ super.updateSelectedRecord();
+
+ if (this._lastSelectedRecordInLayout !== this.selectedRecord) {
+ // Since we don't have the exact element to re-style with a selected appearance
+ // we trigger another layout to re-layout the graph and provide additional
+ // styles for the column for the selected record.
+ this.needsLayout();
+ }
+ }
+
// Private
_updateLegend()
@@ -147,6 +172,48 @@
}
}
+ _graphPositionForMouseEvent(event)
+ {
+ // Only trigger if clicking on a rect, not anywhere in the graph.
+ let elements = document.elementsFromPoint(event.pageX, event.pageY);
+ let rectElement = elements.find((x) => x.localName === "rect");
+ if (!rectElement)
+ return NaN;
+
+ let chartElement = rectElement.closest(".stacked-column-chart");
+ if (!chartElement)
+ return NaN;
+
+ let rect = chartElement.getBoundingClientRect();
+ let position = event.pageX - rect.left;
+
+ if (WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL)
+ return rect.width - position;
+ return position;
+ }
+
+ _handleChartClick(event)
+ {
+ let position = this._graphPositionForMouseEvent(event);
+ if (isNaN(position))
+ return;
+
+ let secondsPerPixel = this.timelineOverview.secondsPerPixel;
+ let graphClickTime = position * secondsPerPixel;
+ let graphStartTime = this.startTime;
+
+ let clickTime = graphStartTime + graphClickTime;
+ let record = this._cpuTimeline.closestRecordTo(clickTime);
+ if (!record)
+ return;
+
+ // Ensure that the container "click" listener added by `WI.TimelineOverview` isn't called.
+ event.__timelineRecordClickEventHandled = true;
+
+ this.selectedRecord = record;
+ this.needsLayout();
+ }
+
_cpuTimelineRecordAdded(event)
{
let cpuTimelineRecord = event.data.record;
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/StackedColumnChart.js (242566 => 242567)
--- trunk/Source/WebInspectorUI/UserInterface/Views/StackedColumnChart.js 2019-03-06 22:28:27 UTC (rev 242566)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/StackedColumnChart.js 2019-03-06 22:28:30 UTC (rev 242567)
@@ -84,11 +84,11 @@
this._sections = sectionClassNames;
}
- addColumnSet(x, totalHeight, width, heights)
+ addColumnSet(x, totalHeight, width, heights, additionalClass)
{
console.assert(heights.length === this._sections.length, "Wrong number of sections in columns set", heights.length, this._sections.length);
- this._columns.push({x, totalHeight, width, heights});
+ this._columns.push({x, totalHeight, width, heights, additionalClass});
}
clear()
@@ -107,7 +107,7 @@
this._svgElement.removeChildren();
- for (let {x, totalHeight, width, heights} of this._columns) {
+ for (let {x, totalHeight, width, heights, additionalClass} of this._columns) {
for (let i = heights.length - 1; i >= 0; --i) {
let height = heights[i];
// Next rect will be identical, skip this one.
@@ -116,6 +116,8 @@
let y = totalHeight - height;
let rect = this._svgElement.appendChild(createSVGElement("rect"));
rect.classList.add(this._sections[i]);
+ if (additionalClass)
+ rect.classList.add(additionalClass);
rect.setAttribute("width", width);
rect.setAttribute("height", height);
rect.setAttribute("x", x);
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js (242566 => 242567)
--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js 2019-03-06 22:28:27 UTC (rev 242566)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js 2019-03-06 22:28:30 UTC (rev 242567)
@@ -688,7 +688,7 @@
_handleGraphsContainerClick(event)
{
// Set when a WI.TimelineRecordBar receives the "click" first and is about to be selected.
- if (event.__timelineRecordBarClick)
+ if (event.__timelineRecordClickEventHandled)
return;
this._recordSelected(null, null);
@@ -766,7 +766,11 @@
let startTime = firstRecord instanceof WI.RenderingFrameTimelineRecord ? firstRecord.frameIndex : firstRecord.startTime;
let endTime = lastRecord instanceof WI.RenderingFrameTimelineRecord ? lastRecord.frameIndex : lastRecord.endTime;
- if (startTime < this.selectionStartTime || endTime > this.selectionStartTime + this.selectionDuration) {
+ if (firstRecord instanceof WI.CPUTimelineRecord) {
+ let selectionPadding = WI.CPUTimelineOverviewGraph.samplingRatePerSecond * 2.25;
+ this.selectionStartTime = startTime - selectionPadding;
+ this.selectionDuration = endTime - startTime + (selectionPadding * 2);
+ } else if (startTime < this.selectionStartTime || endTime > this.selectionStartTime + this.selectionDuration) {
let selectionPadding = this.secondsPerPixel * 10;
this.selectionStartTime = startTime - selectionPadding;
this.selectionDuration = endTime - startTime + (selectionPadding * 2);
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js (242566 => 242567)
--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js 2019-03-06 22:28:27 UTC (rev 242566)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js 2019-03-06 22:28:30 UTC (rev 242567)
@@ -204,7 +204,10 @@
return 36;
}
- get selected() { return this._selected; }
+ get selected()
+ {
+ return this._selected;
+ }
set selected(x)
{
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordBar.js (242566 => 242567)
--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordBar.js 2019-03-06 22:28:27 UTC (rev 242566)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordBar.js 2019-03-06 22:28:30 UTC (rev 242567)
@@ -389,7 +389,7 @@
_handleClick(event)
{
// Ensure that the container "click" listener added by `WI.TimelineOverview` isn't called.
- event.__timelineRecordBarClick = true;
+ event.__timelineRecordClickEventHandled = true;
if (this._delegate.timelineRecordBarClicked)
this._delegate.timelineRecordBarClicked(this);
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRuler.js (242566 => 242567)
--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRuler.js 2019-03-06 22:28:27 UTC (rev 242566)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRuler.js 2019-03-06 22:28:30 UTC (rev 242567)
@@ -774,7 +774,10 @@
continue;
// Clone the event to dispatch it on the new element.
- newTarget.dispatchEvent(new event.constructor(event.type, event));
+ let newEvent = new event.constructor(event.type, event);
+ newTarget.dispatchEvent(newEvent);
+ if (newEvent.__timelineRecordClickEventHandled)
+ event.stop();
return;
}
}