Skip to content

Commit 8da9c5b

Browse files
Nancy LiDevtools-frontend LUCI CQ
authored andcommitted
[RPP Icicle blowtorch] Add a dialog for ignore list in RPP
Now it only has the basic UI, and only allows users to toggle ignore list rules. More features will be added in the future CLs. Screencast: https://screencast.googleplex.com/cast/NDg4MDU3MzgzNDE5OTA0MHxkZTkzMDYyNi05NQ Bug: 376658252 Change-Id: I152bcdb77d28a33f05bd4585061e287a18937511 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6037604 Commit-Queue: Nancy Li <[email protected]> Reviewed-by: Jack Franklin <[email protected]>
1 parent a9b959d commit 8da9c5b

File tree

13 files changed

+296
-6
lines changed

13 files changed

+296
-6
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ grd_files_release_sources = [
8585
"front_end/Images/colon.svg",
8686
"front_end/Images/color-picker-filled.svg",
8787
"front_end/Images/color-picker.svg",
88+
"front_end/Images/compress.svg",
8889
"front_end/Images/console-conditional-breakpoint.svg",
8990
"front_end/Images/console-logpoint.svg",
9091
"front_end/Images/cookie.svg",
@@ -1821,6 +1822,7 @@ grd_files_debug_sources = [
18211822
"front_end/panels/timeline/components/CPUThrottlingSelector.js",
18221823
"front_end/panels/timeline/components/DetailsView.js",
18231824
"front_end/panels/timeline/components/FieldSettingsDialog.js",
1825+
"front_end/panels/timeline/components/IgnoreListSetting.js",
18241826
"front_end/panels/timeline/components/InteractionBreakdown.js",
18251827
"front_end/panels/timeline/components/LayoutShiftDetails.js",
18261828
"front_end/panels/timeline/components/LiveMetricsView.js",
@@ -1840,6 +1842,7 @@ grd_files_debug_sources = [
18401842
"front_end/panels/timeline/components/breadcrumbsUI.css.js",
18411843
"front_end/panels/timeline/components/cpuThrottlingSelector.css.js",
18421844
"front_end/panels/timeline/components/fieldSettingsDialog.css.js",
1845+
"front_end/panels/timeline/components/ignoreListSetting.css.js",
18431846
"front_end/panels/timeline/components/insights/BaseInsightComponent.js",
18441847
"front_end/panels/timeline/components/insights/CLSCulprits.js",
18451848
"front_end/panels/timeline/components/insights/DocumentLatency.js",

config/gni/devtools_image_files.gni

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ devtools_svg_sources = [
8989
"colon.svg",
9090
"color-picker-filled.svg",
9191
"color-picker.svg",
92+
"compress.svg",
9293
"console-conditional-breakpoint.svg",
9394
"console-logpoint.svg",
9495
"cookie.svg",

front_end/Images/src/compress.svg

Lines changed: 8 additions & 0 deletions
Loading

front_end/panels/timeline/TimelinePanel.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import * as Workspace from '../../models/workspace/workspace.js';
4646
import * as TraceBounds from '../../services/trace_bounds/trace_bounds.js';
4747
import * as Adorners from '../../ui/components/adorners/adorners.js';
4848
import type * as Buttons from '../../ui/components/buttons/buttons.js';
49-
import * as ShortcutDialog from '../../ui/components/dialogs/dialogs.js';
49+
import * as Dialogs from '../../ui/components/dialogs/dialogs.js';
5050
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
5151
import * as UI from '../../ui/legacy/legacy.js';
5252
import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
@@ -458,7 +458,7 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
458458
#pendingAriaMessage: string|null = null;
459459

460460
#eventToRelatedInsights: TimelineComponents.RelatedInsightChips.EventToRelatedInsightsMap = new Map();
461-
#shortcutsDialog: ShortcutDialog.ShortcutDialog.ShortcutDialog = new ShortcutDialog.ShortcutDialog.ShortcutDialog();
461+
#shortcutsDialog: Dialogs.ShortcutDialog.ShortcutDialog = new Dialogs.ShortcutDialog.ShortcutDialog();
462462

463463
#onMainEntryHovered: (event: Common.EventTarget.EventTargetEvent<number>) => void;
464464

@@ -1083,9 +1083,14 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
10831083
// GC
10841084
this.panelToolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButtonForId('components.collect-garbage'));
10851085

1086+
// Ignore list setting
1087+
this.panelToolbar.appendSeparator();
1088+
const showIgnoreListSetting = new TimelineComponents.IgnoreListSetting.IgnoreListSetting();
1089+
this.panelToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(showIgnoreListSetting));
1090+
10861091
// Isolate selector
1087-
const isolateSelector = new IsolateSelector();
10881092
if (isNode) {
1093+
const isolateSelector = new IsolateSelector();
10891094
this.panelToolbar.appendSeparator();
10901095
this.panelToolbar.appendToolbarItem(isolateSelector);
10911096
}
@@ -1130,7 +1135,7 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
11301135
return navigationRadioButtons;
11311136
}
11321137

1133-
#getShortcutsInfo(isNavClassic: boolean): ShortcutDialog.ShortcutDialog.Shortcut[] {
1138+
#getShortcutsInfo(isNavClassic: boolean): Dialogs.ShortcutDialog.Shortcut[] {
11341139
if (isNavClassic) {
11351140
return [
11361141
{title: i18nString(UIStrings.timelineScrollUpDown), bindings: [['Shift', 'Scroll']]},

front_end/panels/timeline/components/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ generate_css("css_files") {
1212
"breadcrumbsUI.css",
1313
"cpuThrottlingSelector.css",
1414
"fieldSettingsDialog.css",
15+
"ignoreListSetting.css",
1516
"interactionBreakdown.css",
1617
"layoutShiftDetails.css",
1718
"liveMetricsView.css",
@@ -36,6 +37,7 @@ devtools_module("components") {
3637
"CPUThrottlingSelector.ts",
3738
"DetailsView.ts",
3839
"FieldSettingsDialog.ts",
40+
"IgnoreListSetting.ts",
3941
"InteractionBreakdown.ts",
4042
"LayoutShiftDetails.ts",
4143
"LiveMetricsView.ts",
@@ -110,6 +112,7 @@ ts_library("unittests") {
110112
"BreadcrumbsUI.test.ts",
111113
"CPUThrottlingSelector.test.ts",
112114
"FieldSettingsDialog.test.ts",
115+
"IgnoreListSetting.test.ts",
113116
"InteractionBreakdown.test.ts",
114117
"Invalidations.test.ts",
115118
"LayoutShiftDetails.test.ts",
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as Common from '../../../core/common/common.js';
6+
import * as SDK from '../../../core/sdk/sdk.js';
7+
import * as Bindings from '../../../models/bindings/bindings.js';
8+
import * as Workspace from '../../../models/workspace/workspace.js';
9+
import {renderElementIntoDOM} from '../../../testing/DOMHelpers.js';
10+
import {describeWithEnvironment} from '../../../testing/EnvironmentHelpers.js';
11+
import * as Coordinator from '../../../ui/components/render_coordinator/render_coordinator.js';
12+
13+
import * as TimelineComponents from './components.js';
14+
15+
describeWithEnvironment('isIgnoreListedEntry', () => {
16+
async function renderIgnoreListSetting(): Promise<HTMLElement> {
17+
const coordinator = Coordinator.RenderCoordinator.RenderCoordinator.instance();
18+
const component = new TimelineComponents.IgnoreListSetting.IgnoreListSetting();
19+
renderElementIntoDOM(component);
20+
await coordinator.done();
21+
return component;
22+
}
23+
24+
function getAllRules(component: HTMLElement): {regex: string, disabled: boolean}[] {
25+
assert.isNotNull(component.shadowRoot);
26+
const regexRows = component.shadowRoot.querySelectorAll<HTMLElement>('.regex-row');
27+
return Array.from(regexRows).map(row => {
28+
const checkboxShadow = row.querySelector('dt-checkbox')?.shadowRoot;
29+
assert.exists(checkboxShadow);
30+
return {
31+
regex: checkboxShadow.querySelector('label')?.textContent?.trim() ?? '',
32+
disabled: !checkboxShadow.querySelector('input')?.checked,
33+
};
34+
});
35+
}
36+
37+
before(() => {
38+
const targetManager = SDK.TargetManager.TargetManager.instance();
39+
const workspace = Workspace.Workspace.WorkspaceImpl.instance({forceNew: true});
40+
const resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace);
41+
const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(
42+
{forceNew: true, resourceMapping, targetManager});
43+
Bindings.IgnoreListManager.IgnoreListManager.instance({
44+
forceNew: true,
45+
debuggerWorkspaceBinding,
46+
});
47+
48+
ignoreRegex('rule 1');
49+
});
50+
51+
it('Able to render the ignore listed rules', async () => {
52+
const component = await renderIgnoreListSetting();
53+
const ignoredRules = getAllRules(component);
54+
55+
// There is a default rule `/node_modules/|/bower_components/`
56+
assert.deepStrictEqual(ignoredRules.length, 2);
57+
assert.deepStrictEqual(ignoredRules[0].regex, '/node_modules/|/bower_components/');
58+
assert.deepStrictEqual(ignoredRules[0].disabled, false);
59+
assert.deepStrictEqual(ignoredRules[1].regex, 'rule 1');
60+
assert.deepStrictEqual(ignoredRules[1].disabled, false);
61+
});
62+
63+
it('Able to render the disabled ignore listed rules', async () => {
64+
disableIgnoreRegex('rule 1');
65+
66+
const component = await renderIgnoreListSetting();
67+
const ignoredRules = getAllRules(component);
68+
69+
// There is a default rule `/node_modules/|/bower_components/`
70+
assert.deepStrictEqual(ignoredRules.length, 2);
71+
assert.deepStrictEqual(ignoredRules[0].regex, '/node_modules/|/bower_components/');
72+
assert.deepStrictEqual(ignoredRules[0].disabled, false);
73+
assert.deepStrictEqual(ignoredRules[1].regex, 'rule 1');
74+
assert.deepStrictEqual(ignoredRules[1].disabled, true);
75+
});
76+
77+
it('Able to toggle the disable status of an ignore listed rules', async () => {
78+
const component = await renderIgnoreListSetting();
79+
80+
assert.isNotNull(component.shadowRoot);
81+
const regexRows = component.shadowRoot.querySelectorAll<HTMLElement>('.regex-row');
82+
// "rule 1" is the second in the view.
83+
// Now the "rule 1" is disabled (by last test), so click the checkbox, it will be enabled.
84+
// Add sanity checks to make sure.
85+
const checkboxShadow = regexRows[1].querySelector('dt-checkbox')?.shadowRoot;
86+
assert.strictEqual(checkboxShadow?.querySelector('label')?.textContent, 'rule 1');
87+
assert.isTrue(isIgnoreRegexDisabled('rule 1'));
88+
89+
const rule1CheckBox = checkboxShadow?.querySelector('input');
90+
rule1CheckBox?.click();
91+
assert.isFalse(isIgnoreRegexDisabled('rule 1'));
92+
});
93+
});
94+
95+
function ignoreRegex(regexValue: string): void {
96+
const regexPatterns =
97+
(Common.Settings.Settings.instance().moduleSetting('skip-stack-frames-pattern') as Common.Settings.RegExpSetting)
98+
.getAsArray();
99+
regexPatterns.push({pattern: regexValue, disabled: false});
100+
}
101+
102+
function disableIgnoreRegex(regexValue: string): void {
103+
const regexPatterns =
104+
(Common.Settings.Settings.instance().moduleSetting('skip-stack-frames-pattern') as Common.Settings.RegExpSetting)
105+
.getAsArray();
106+
for (const regexPattern of regexPatterns) {
107+
if (regexPattern.pattern === regexValue) {
108+
regexPattern.disabled = true;
109+
break;
110+
}
111+
}
112+
}
113+
114+
function isIgnoreRegexDisabled(regexValue: string): boolean {
115+
const regexPatterns =
116+
(Common.Settings.Settings.instance().moduleSetting('skip-stack-frames-pattern') as Common.Settings.RegExpSetting)
117+
.getAsArray();
118+
for (const regexPattern of regexPatterns) {
119+
if (regexPattern.pattern === regexValue) {
120+
return regexPattern.disabled ?? false;
121+
}
122+
}
123+
return false;
124+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import '../../../ui/components/menus/menus.js';
6+
7+
import * as Common from '../../../core/common/common.js';
8+
import * as i18n from '../../../core/i18n/i18n.js';
9+
import * as Buttons from '../../../ui/components/buttons/buttons.js';
10+
import * as Dialogs from '../../../ui/components/dialogs/dialogs.js';
11+
import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
12+
import * as UI from '../../../ui/legacy/legacy.js';
13+
import * as LitHtml from '../../../ui/lit-html/lit-html.js';
14+
15+
import ignoreListSettingStyles from './ignoreListSetting.css.js';
16+
17+
const {html} = LitHtml;
18+
19+
const UIStrings = {
20+
/**
21+
* @description Text title for the button to open the ignore list setting.
22+
*/
23+
showIgnoreListSettingDialog: 'Show ignore list setting dialog',
24+
/**
25+
* @description Text title for ignore list setting.
26+
*/
27+
ignoreList: 'Ignore list',
28+
/**
29+
* @description Text description for ignore list setting.
30+
*/
31+
ignoreListDescription: 'Add these exclusion rules would simplify the flame chart.',
32+
/**
33+
*@description Pattern title in Framework Ignore List Settings Tab of the Settings
34+
*@example {ad.*?} regex
35+
*/
36+
ignoreScriptsWhoseNamesMatchS: 'Ignore scripts whose names match \'\'{regex}\'\'',
37+
};
38+
39+
const str_ = i18n.i18n.registerUIStrings('panels/timeline/components/IgnoreListSetting.ts', UIStrings);
40+
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
41+
42+
export class IgnoreListSetting extends HTMLElement {
43+
readonly #shadow = this.attachShadow({mode: 'open'});
44+
readonly #renderBound = this.#render.bind(this);
45+
readonly #ignoreListEnabled: Common.Settings.Setting<boolean> =
46+
Common.Settings.Settings.instance().moduleSetting('enable-ignore-listing');
47+
readonly #regexPatterns = this.#getSkipStackFramesPatternSetting().getAsArray();
48+
49+
constructor() {
50+
super();
51+
52+
Common.Settings.Settings.instance()
53+
.moduleSetting('skip-stack-frames-pattern')
54+
.addChangeListener(this.#scheduleRender.bind(this));
55+
Common.Settings.Settings.instance()
56+
.moduleSetting('enable-ignore-listing')
57+
.addChangeListener(this.#scheduleRender.bind(this));
58+
}
59+
60+
connectedCallback(): void {
61+
this.#shadow.adoptedStyleSheets = [ignoreListSettingStyles];
62+
this.#scheduleRender();
63+
}
64+
65+
#scheduleRender(): void {
66+
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#renderBound);
67+
}
68+
69+
#getSkipStackFramesPatternSetting(): Common.Settings.RegExpSetting {
70+
return Common.Settings.Settings.instance().moduleSetting('skip-stack-frames-pattern') as
71+
Common.Settings.RegExpSetting;
72+
}
73+
74+
#onRegexEnableToggled(regex: Common.Settings.RegExpSettingItem, checkbox: UI.UIUtils.CheckboxLabel): void {
75+
regex.disabled = !checkbox.checkboxElement.checked;
76+
77+
// Technically we don't need to call the set function, because the regex is a reference, so it changed the setting
78+
// value directly.
79+
// But we need to call the set function to trigger the setting change event. which is needed by view update of flame
80+
// chart.
81+
this.#getSkipStackFramesPatternSetting().setAsArray(this.#regexPatterns);
82+
// There is no need to update this component, since the only UI change is this checkbox, which is already done by
83+
// the user.
84+
}
85+
86+
#renderItem(regex: Common.Settings.RegExpSettingItem): LitHtml.TemplateResult {
87+
const checkbox = UI.UIUtils.CheckboxLabel.create(
88+
regex.pattern, !regex.disabled, /* subtitle*/ undefined, /* jslogContext*/ 'timeline.ignore-list-pattern');
89+
const helpText = i18nString(UIStrings.ignoreScriptsWhoseNamesMatchS, {regex: regex.pattern});
90+
UI.Tooltip.Tooltip.install(checkbox, helpText);
91+
checkbox.checkboxElement.ariaLabel = helpText;
92+
checkbox.checkboxElement.addEventListener('change', this.#onRegexEnableToggled.bind(this, regex, checkbox), false);
93+
return html`
94+
<div class='regex-row'>${checkbox}</div>
95+
`;
96+
}
97+
98+
#render(): void {
99+
if (!ComponentHelpers.ScheduledRender.isScheduledRender(this)) {
100+
throw new Error('Ignore List setting dialog render was not scheduled');
101+
}
102+
// clang-format off
103+
const output = html`
104+
<devtools-button-dialog .data=${{
105+
openOnRender: false,
106+
jslogContext: 'timeline.ignore-list',
107+
variant: Buttons.Button.Variant.TOOLBAR,
108+
iconName: 'compress',
109+
disabled: !this.#ignoreListEnabled.get(),
110+
iconTitle: i18nString(UIStrings.showIgnoreListSettingDialog),
111+
horizontalAlignment: Dialogs.Dialog.DialogHorizontalAlignment.AUTO,
112+
closeButton: true,
113+
dialogTitle: i18nString(UIStrings.ignoreList),
114+
} as Dialogs.ButtonDialog.ButtonDialogData}>
115+
<div class='ignore-list-setting-content'>
116+
<div class='ignore-list-setting-description'>${i18nString(UIStrings.ignoreListDescription)}</div>
117+
${this.#regexPatterns.map(this.#renderItem.bind(this))}
118+
</div>
119+
</devtools-button-dialog>
120+
`;
121+
// clang-format on
122+
LitHtml.render(output, this.#shadow, {host: this});
123+
}
124+
}
125+
126+
customElements.define('devtools-perf-ignore-list-setting', IgnoreListSetting);
127+
128+
declare global {
129+
interface HTMLElementTagNameMap {
130+
'devtools-perf-ignore-list-setting': IgnoreListSetting;
131+
}
132+
}

front_end/panels/timeline/components/components.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as BreadcrumbsUI from './BreadcrumbsUI.js';
77
import * as CPUThrottlingSelector from './CPUThrottlingSelector.js';
88
import * as DetailsView from './DetailsView.js';
99
import * as FieldSettingsDialog from './FieldSettingsDialog.js';
10+
import * as IgnoreListSetting from './IgnoreListSetting.js';
1011
import * as InteractionBreakdown from './InteractionBreakdown.js';
1112
import * as LayoutShiftDetails from './LayoutShiftDetails.js';
1213
import * as LiveMetricsView from './LiveMetricsView.js';
@@ -29,6 +30,7 @@ export {
2930
CPUThrottlingSelector,
3031
DetailsView,
3132
FieldSettingsDialog,
33+
IgnoreListSetting,
3234
InteractionBreakdown,
3335
LayoutShiftDetails,
3436
LiveMetricsView,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright 2024 The Chromium Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style license that can be
4+
* found in the LICENSE file.
5+
*/
6+
.ignore-list-setting-description {
7+
margin-bottom: 5px;
8+
}

0 commit comments

Comments
 (0)