Skip to content

Commit 085d08c

Browse files
paulirishDevtools-frontend LUCI CQ
authored andcommitted
RPP: Fix 3P table scrolling, plus Summary tab polish
- Scrolling was mostly buggy due to a hardcoded row height in DataGrid (20), whereas our rows were not. It's dynamic now and doesn't force a layout thrash either. - All of TimelineTreeView styles have been in timelinePanel.css, but due to shadow DOM, the 3P table wasn't using them. This lead to some unpredictability. Now, 3PTTV extends the component _and_ its styles. - This included moving 3P table styles from timelineSummary.css to timelinePanel.css. That said, I've adjusted 60% of those styles, too. - Reduced DOM complexity of the summary: fewer containers, removed use of slots. Also removed a div within .entity-badge - A few renames to help distinguish Details (the pane with tabs), Summary (the first tab which is often used to show the _details_ of a trace event), the Category Summary (the Scripting/Rendering/etc numbers), and Range Summary (a container holding the Category Summary and 3P table, side by side) - Pixel-perfect vertical rhythm, matching mocks: https://screenshot.googleplex.com/C2Pkr5anVgU3F5o (That was fun :) - Changed how layout of the bottom-up button works, to get more predictable layout behavior. Change-Id: Ia92a4a78e17813e1ce9c64d313e2a978f9aaf7d5 Bug: 388458798 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6221821 Reviewed-by: Jack Franklin <[email protected]> Reviewed-by: Adriana Ixba <[email protected]> Commit-Queue: Jack Franklin <[email protected]>
1 parent 88b4fd6 commit 085d08c

File tree

15 files changed

+213
-185
lines changed

15 files changed

+213
-185
lines changed

front_end/panels/timeline/ThirdPartyTreeView.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ describeWithEnvironment('TimelineTreeView', function() {
5050
firstNode, firstNode.totalTime, firstNode.selfTime, firstNode.totalTime, treeView);
5151
const extEntity = extensionNode?.createCell('site');
5252

53-
let gotBadgeName = extEntity.querySelector<HTMLTableRowElement>('.entity-badge-name')?.textContent || '';
53+
let gotBadgeName = extEntity.querySelector<HTMLTableRowElement>('.entity-badge')?.textContent || '';
5454
assert.strictEqual(gotBadgeName, 'Extension');
5555

5656
// Node with first party
@@ -61,7 +61,7 @@ describeWithEnvironment('TimelineTreeView', function() {
6161
secondNode, secondNode.totalTime, secondNode.selfTime, secondNode.totalTime, treeView);
6262
const firstPartyEntity = firstPartyNode?.createCell('site');
6363

64-
gotBadgeName = firstPartyEntity.querySelector<HTMLTableRowElement>('.entity-badge-name')?.textContent || '';
64+
gotBadgeName = firstPartyEntity.querySelector<HTMLTableRowElement>('.entity-badge')?.textContent || '';
6565
assert.strictEqual(gotBadgeName, '1st party');
6666
});
6767
});

front_end/panels/timeline/ThirdPartyTreeView.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export class ThirdPartyTreeViewWidget extends TimelineTreeView.TimelineTreeView
4949
this.element.setAttribute('jslog', `${VisualLogging.pane('third-party-tree').track({hover: true})}`);
5050
this.init();
5151
this.dataGrid.markColumnAsSortedBy('self', DataGrid.DataGrid.Order.Descending);
52+
this.dataGrid.setResizeMethod(DataGrid.DataGrid.ResizeMethod.NEAREST);
5253
/**
5354
* By default data grids always expand when arrowing.
5455
* For 3P table, we don't use this feature.
@@ -130,21 +131,23 @@ export class ThirdPartyTreeViewWidget extends TimelineTreeView.TimelineTreeView
130131
{
131132
id: 'site',
132133
title: i18nString(UIStrings.firstOrThirdPartyName),
133-
width: '100px',
134-
fixedWidth: true,
134+
// It's important that this width is the `.widget.vbox.timeline-tree-view` max-width (550)
135+
// minus the two fixed sizes below. (550-100-105) == 345
136+
width: '345px',
137+
// And with this column not-fixed-width and resizingMethod NEAREST, the name-column will appropriately flex.
135138
sortable: true,
136139
},
137140
{
138141
id: 'transfer-size',
139142
title: i18nString(UIStrings.transferSize),
140-
width: '80px',
143+
width: '100px', // Mostly so there's room for the header plus sorting triangle
141144
fixedWidth: true,
142145
sortable: true,
143146
},
144147
{
145148
id: 'self',
146149
title: i18nString(UIStrings.selfTime),
147-
width: '80px',
150+
width: '105px', // Mostly to fit large self-time plus devtools-button
148151
fixedWidth: true,
149152
sortable: true,
150153
});
@@ -257,7 +260,7 @@ export class ThirdPartyTreeViewWidget extends TimelineTreeView.TimelineTreeView
257260
}
258261
}
259262

260-
export class ThirdPartyTreeView extends UI.Widget.WidgetElement<UI.Widget.Widget> {
263+
export class ThirdPartyTreeElement extends UI.Widget.WidgetElement<UI.Widget.Widget> {
261264
#treeView?: ThirdPartyTreeViewWidget;
262265

263266
set treeView(treeView: ThirdPartyTreeViewWidget) {
@@ -279,10 +282,10 @@ export class ThirdPartyTreeView extends UI.Widget.WidgetElement<UI.Widget.Widget
279282
}
280283
}
281284

282-
customElements.define('devtools-performance-third-party-tree-view', ThirdPartyTreeView);
285+
customElements.define('devtools-performance-third-party-tree-view', ThirdPartyTreeElement);
283286

284287
declare global {
285288
interface HTMLElementTagNameMap {
286-
'devtools-performance-third-party-tree-view': ThirdPartyTreeView;
289+
'devtools-performance-third-party-tree-view': ThirdPartyTreeElement;
287290
}
288291
}

front_end/panels/timeline/TimelineDetailsView.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describeWithEnvironment('TimelineDetailsView', function() {
2626

2727
it('displays the details of a network request event correctly', async function() {
2828
const {parsedTrace, insights} = await TraceLoader.traceEngine(this, 'lcp-web-font.json.gz');
29-
const detailsView = new Timeline.TimelineDetailsView.TimelineDetailsView(mockViewDelegate);
29+
const detailsView = new Timeline.TimelineDetailsView.TimelineDetailsPane(mockViewDelegate);
3030

3131
const networkRequests = parsedTrace.NetworkRequests.byTime;
3232
const cssRequest = networkRequests.find(request => {
@@ -53,7 +53,7 @@ describeWithEnvironment('TimelineDetailsView', function() {
5353

5454
it('displays the details for a frame correctly', async function() {
5555
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
56-
const detailsView = new Timeline.TimelineDetailsView.TimelineDetailsView(mockViewDelegate);
56+
const detailsView = new Timeline.TimelineDetailsView.TimelineDetailsPane(mockViewDelegate);
5757
await detailsView.setModel({
5858
parsedTrace,
5959
selectedEvents: null,
@@ -79,7 +79,7 @@ describeWithEnvironment('TimelineDetailsView', function() {
7979

8080
it('renders the layout shift component for a single layout shift', async function() {
8181
const {parsedTrace} = await TraceLoader.traceEngine(this, 'shift-attribution.json.gz');
82-
const detailsView = new Timeline.TimelineDetailsView.TimelineDetailsView(mockViewDelegate);
82+
const detailsView = new Timeline.TimelineDetailsView.TimelineDetailsPane(mockViewDelegate);
8383
await detailsView.setModel({
8484
parsedTrace,
8585
selectedEvents: null,
@@ -101,7 +101,7 @@ describeWithEnvironment('TimelineDetailsView', function() {
101101

102102
it('renders the layout shift component for a selected cluster', async function() {
103103
const {parsedTrace} = await TraceLoader.traceEngine(this, 'shift-attribution.json.gz');
104-
const detailsView = new Timeline.TimelineDetailsView.TimelineDetailsView(mockViewDelegate);
104+
const detailsView = new Timeline.TimelineDetailsView.TimelineDetailsPane(mockViewDelegate);
105105
await detailsView.setModel({
106106
parsedTrace,
107107
selectedEvents: null,
@@ -123,7 +123,7 @@ describeWithEnvironment('TimelineDetailsView', function() {
123123

124124
it('updates the range details when the user has a range selected', async function() {
125125
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
126-
const detailsView = new Timeline.TimelineDetailsView.TimelineDetailsView(mockViewDelegate);
126+
const detailsView = new Timeline.TimelineDetailsView.TimelineDetailsPane(mockViewDelegate);
127127
await detailsView.setModel({
128128
parsedTrace,
129129
// We have to set selected events for the range selection UI to be drawn

front_end/panels/timeline/TimelineDetailsView.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const UIStrings = {
6363
};
6464
const str_ = i18n.i18n.registerUIStrings('panels/timeline/TimelineDetailsView.ts', UIStrings);
6565
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
66-
export class TimelineDetailsView extends
66+
export class TimelineDetailsPane extends
6767
Common.ObjectWrapper.eventMixin<TimelineTreeView.EventTypes, typeof UI.Widget.VBox>(UI.Widget.VBox) {
6868
private readonly detailsLinkifier: Components.Linkifier.Linkifier;
6969
private tabbedPane: UI.TabbedPane.TabbedPane;
@@ -260,7 +260,7 @@ export class TimelineDetailsView extends
260260
await this.setSelection(null);
261261
}
262262

263-
private setContent(node: Node): void {
263+
private setSummaryContent(node: Node): void {
264264
const allTabs = this.tabbedPane.otherTabs(Tab.Details);
265265
for (let i = 0; i < allTabs.length; ++i) {
266266
if (!this.rangeDetailViews.has(allTabs[i])) {
@@ -269,7 +269,7 @@ export class TimelineDetailsView extends
269269
}
270270
this.defaultDetailsContentWidget.detach();
271271
this.defaultDetailsContentWidget = this.#createContentWidget();
272-
this.defaultDetailsContentWidget.contentElement.appendChild(node);
272+
this.defaultDetailsContentWidget.contentElement.append(node);
273273
if (this.#relatedInsightChips) {
274274
this.defaultDetailsContentWidget.contentElement.appendChild(this.#relatedInsightChips);
275275
}
@@ -318,7 +318,7 @@ export class TimelineDetailsView extends
318318
*/
319319
private scheduleUpdateContentsFromWindow(forceImmediateUpdate: boolean = false): void {
320320
if (!this.#parsedTrace) {
321-
this.setContent(UI.Fragment.html`<div/>`);
321+
this.setSummaryContent(UI.Fragment.html`<div/>`);
322322
return;
323323
}
324324
if (forceImmediateUpdate) {
@@ -361,7 +361,8 @@ export class TimelineDetailsView extends
361361

362362
#setSelectionForTimelineFrame(frame: Trace.Types.Events.LegacyTimelineFrame): void {
363363
const matchedFilmStripFrame = this.#getFilmStripFrame(frame);
364-
this.setContent(TimelineUIUtils.generateDetailsContentForFrame(frame, this.#filmStrip, matchedFilmStripFrame));
364+
this.setSummaryContent(
365+
TimelineUIUtils.generateDetailsContentForFrame(frame, this.#filmStrip, matchedFilmStripFrame));
365366
const target = SDK.TargetManager.TargetManager.instance().rootTarget();
366367
if (frame.layerTree && target) {
367368
const layerTreeForFrame = new TimelineModel.TracingLayerTree.TracingFrameLayerTree(target, frame.layerTree);
@@ -384,7 +385,7 @@ export class TimelineDetailsView extends
384385
this.#relatedInsightChips.eventToRelatedInsightsMap = this.#eventToRelatedInsightsMap;
385386
}
386387

387-
this.setContent(this.#networkRequestDetails);
388+
this.setSummaryContent(this.#networkRequestDetails);
388389
}
389390

390391
async #setSelectionForTraceEvent(event: Trace.Types.Events.Event): Promise<void> {
@@ -402,7 +403,7 @@ export class TimelineDetailsView extends
402403
if (Trace.Types.Events.isSyntheticLayoutShift(event) || Trace.Types.Events.isSyntheticLayoutShiftCluster(event)) {
403404
const isFreshRecording = Boolean(this.#parsedTrace && Tracker.instance().recordingIsFresh(this.#parsedTrace));
404405
this.#layoutShiftDetails.setData(event, this.#traceInsightsSets, this.#parsedTrace, isFreshRecording);
405-
this.setContent(this.#layoutShiftDetails);
406+
this.setSummaryContent(this.#layoutShiftDetails);
406407
return;
407408
}
408409

@@ -497,7 +498,7 @@ export class TimelineDetailsView extends
497498
}
498499

499500
private appendDetailsTabsForTraceEventAndShowDetails(event: Trace.Types.Events.Event, content: Node): void {
500-
this.setContent(content);
501+
this.setSummaryContent(content);
501502
if (Trace.Types.Events.isPaint(event) || Trace.Types.Events.isRasterTask(event)) {
502503
this.showEventInPaintProfiler(event);
503504
}
@@ -536,9 +537,10 @@ export class TimelineDetailsView extends
536537
const aggregatedStats = TimelineUIUtils.statsForTimeRange(this.#selectedEvents, startTime, endTime);
537538
const startOffset = startTime - minBoundsMilli;
538539
const endOffset = endTime - minBoundsMilli;
539-
const summaryDetails = TimelineUIUtils.generateSummaryDetails(
540+
const summaryDetailElem = TimelineUIUtils.generateSummaryDetails(
540541
aggregatedStats, startOffset, endOffset, this.#selectedEvents, this.#thirdPartyTree);
541-
this.setContent(summaryDetails);
542+
543+
this.setSummaryContent(summaryDetailElem);
542544

543545
// Find all recalculate style events data from range
544546
const isSelectorStatsEnabled =

front_end/panels/timeline/TimelineFlameChartView.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {ModificationsManager} from './ModificationsManager.js';
2323
import * as OverlayComponents from './overlays/components/components.js';
2424
import * as Overlays from './overlays/overlays.js';
2525
import {targetForEvent} from './TargetForEvent.js';
26-
import {TimelineDetailsView} from './TimelineDetailsView.js';
26+
import {TimelineDetailsPane} from './TimelineDetailsView.js';
2727
import {TimelineRegExp} from './TimelineFilters.js';
2828
import {
2929
Events as TimelineFlameChartDataProviderEvents,
@@ -107,7 +107,7 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
107107
private brickGame?: PerfUI.BrickBreaker.BrickBreaker;
108108
private readonly countersView: CountersGraph;
109109
private readonly detailsSplitWidget: UI.SplitWidget.SplitWidget;
110-
private readonly detailsView: TimelineDetailsView;
110+
private readonly detailsView: TimelineDetailsPane;
111111
private readonly onMainAddEntryLabelAnnotation: (event: Common.EventTarget.EventTargetEvent<{
112112
entryIndex: number,
113113
withLinkCreationButton: boolean,
@@ -346,7 +346,7 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
346346
// Create top level properties splitter.
347347
this.detailsSplitWidget = new UI.SplitWidget.SplitWidget(false, true, 'timeline-panel-details-split-view-state');
348348
this.detailsSplitWidget.element.classList.add('timeline-details-split');
349-
this.detailsView = new TimelineDetailsView(delegate);
349+
this.detailsView = new TimelineDetailsPane(delegate);
350350
this.detailsSplitWidget.installResizer(this.detailsView.headerElement());
351351
this.detailsSplitWidget.setMainWidget(this.chartSplitWidget);
352352
this.detailsSplitWidget.setSidebarWidget(this.detailsView);

front_end/panels/timeline/TimelineTreeView.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -735,8 +735,8 @@ export class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<Gri
735735

736736
if (badgeText) {
737737
const badge = container.createChild('div', 'entity-badge');
738+
badge.textContent = badgeText;
738739
UI.ARIAUtils.setLabel(badge, badgeText);
739-
badge.createChild('div', 'entity-badge-name').textContent = badgeText;
740740
}
741741
}
742742
} else if (event) {
@@ -817,23 +817,19 @@ export class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<Gri
817817
i18nString(UIStrings.percentPlaceholder, {PH1: (value / this.grandTotalTime * 100).toFixed(1)});
818818
}
819819
if (maxTime) {
820-
textDiv.classList.add('background-percent-bar');
820+
textDiv.classList.add('background-bar-text');
821821
cell.createChild('div', 'background-bar-container').createChild('div', 'background-bar').style.width =
822822
(value * 100 / maxTime).toFixed(1) + '%';
823823
}
824824
// Generate button on hover for 3P self time cell.
825825
if (showBottomUpButton) {
826-
this.generateBottomUpButton(cell);
826+
this.generateBottomUpButton(textDiv);
827827
}
828828
return cell;
829829
}
830830

831831
// Generates bottom up tree hover button and appends it to the provided cell element.
832-
private generateBottomUpButton(cell: HTMLElement): void {
833-
const buttonContainer = document.createElement('div');
834-
buttonContainer.className = 'button-container';
835-
cell.classList.add('hover-bottom-up-button');
836-
832+
private generateBottomUpButton(textDiv: HTMLElement): void {
837833
const button = new Buttons.Button.Button();
838834
button.data = {
839835
variant: Buttons.Button.Variant.ICON,
@@ -843,11 +839,10 @@ export class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<Gri
843839
};
844840
UI.ARIAUtils.setLabel(button, i18nString(UIStrings.viewBottomUp));
845841
button.addEventListener('click', () => this.#bottomUpButtonClicked());
846-
buttonContainer.appendChild(button);
847842
UI.Tooltip.Tooltip.install(button, i18nString(UIStrings.bottomUp));
848843

849844
// Append the button to the last column
850-
cell.appendChild(buttonContainer);
845+
textDiv.appendChild(button);
851846
}
852847

853848
#bottomUpButtonClicked(): void {

front_end/panels/timeline/TimelineUIUtils.ts

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,17 +2187,17 @@ export class TimelineUIUtils {
21872187
aggregatedStats: Record<string, number>, rangeStart: number, rangeEnd: number,
21882188
selectedEvents: Trace.Types.Events.Event[],
21892189
thirdPartyTree: ThirdPartyTreeView.ThirdPartyTreeViewWidget): Element {
2190+
const element = document.createElement('div');
2191+
element.classList.add('timeline-details-range-summary', 'hbox');
2192+
2193+
// First, the category bar chart.
21902194
let total = 0;
2195+
let categories: TimelineComponents.TimelineSummary.CategoryData[] = [];
21912196
// Calculate total of all categories.
21922197
for (const categoryName in aggregatedStats) {
21932198
total += aggregatedStats[categoryName];
21942199
}
21952200

2196-
const element = document.createElement('div');
2197-
element.classList.add('timeline-details-view-summary');
2198-
2199-
let categories: TimelineComponents.TimelineSummary.CategoryData[] = [];
2200-
22012201
// Get stats values from categories.
22022202
for (const categoryName in Utils.EntryStyles.getCategoryStyles()) {
22032203
const category = Utils.EntryStyles.getCategoryStyles()[categoryName as keyof Utils.EntryStyles.CategoryPalette];
@@ -2217,36 +2217,24 @@ export class TimelineUIUtils {
22172217
categories = categories.sort((a, b) => b.value - a.value);
22182218
const start = Trace.Types.Timing.Milli(rangeStart);
22192219
const end = Trace.Types.Timing.Milli(rangeEnd);
2220-
const summaryTable = new TimelineComponents.TimelineSummary.TimelineSummary();
2221-
summaryTable.data = {
2220+
const categorySummaryTable = new TimelineComponents.TimelineSummary.CategorySummary();
2221+
categorySummaryTable.data = {
22222222
rangeStart: start,
22232223
rangeEnd: end,
22242224
total,
22252225
categories,
22262226
selectedEvents,
22272227
};
2228-
const summaryTableContainer = element.createChild('div');
2229-
summaryTableContainer.appendChild(summaryTable);
2228+
element.append(categorySummaryTable);
22302229

2231-
if (!Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.TIMELINE_THIRD_PARTY_DEPENDENCIES)) {
2232-
return element;
2230+
if (Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.TIMELINE_THIRD_PARTY_DEPENDENCIES)) {
2231+
// Add the 3p datagrid
2232+
const treeView = new ThirdPartyTreeView.ThirdPartyTreeElement();
2233+
treeView.treeView = thirdPartyTree;
2234+
UI.ARIAUtils.setLabel(treeView, i18nString(UIStrings.thirdPartyTable));
2235+
element.append(treeView);
22332236
}
22342237

2235-
const treeView = new ThirdPartyTreeView.ThirdPartyTreeView();
2236-
treeView.treeView = thirdPartyTree;
2237-
const treeSlot = document.createElement('slot');
2238-
2239-
const thirdPartyDiv = document.createElement('div');
2240-
thirdPartyDiv.className = 'third-party-table';
2241-
UI.ARIAUtils.setLabel(thirdPartyDiv, i18nString(UIStrings.thirdPartyTable));
2242-
2243-
treeSlot.name = 'third-party-table';
2244-
treeSlot.append(treeView);
2245-
2246-
thirdPartyDiv.appendChild(treeSlot);
2247-
if (summaryTable.shadowRoot) {
2248-
summaryTable.shadowRoot?.appendChild(thirdPartyDiv);
2249-
}
22502238
return element;
22512239
}
22522240

front_end/panels/timeline/components/TimelineSummary.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describeWithMockConnection('TimelineSummary', () => {
1616
{title: 'Rendering', value: 0, color: 'black'},
1717
];
1818

19-
const summary = new TimelineComponents.TimelineSummary.TimelineSummary();
19+
const summary = new TimelineComponents.TimelineSummary.CategorySummary();
2020
summary.data = {
2121
rangeStart: 0,
2222
rangeEnd: 110,
@@ -45,7 +45,7 @@ describeWithMockConnection('TimelineSummary', () => {
4545
it('no categories should just render Total', async function() {
4646
const categories: TimelineComponents.TimelineSummary.CategoryData[] = [];
4747

48-
const summary = new TimelineComponents.TimelineSummary.TimelineSummary();
48+
const summary = new TimelineComponents.TimelineSummary.CategorySummary();
4949
summary.data = {
5050
rangeStart: 0,
5151
rangeEnd: 110,

0 commit comments

Comments
 (0)