chrome-devtools-frontend 1.0.1698733 → 1.0.1699061

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.agents/skills/devtools-verification/SKILL.md +19 -0
  2. package/.agents/skills/{repro-flaky-tests → fix-tests}/SKILL.md +2 -2
  3. package/front_end/core/sdk/DOMModel.ts +3 -5
  4. package/front_end/core/sdk/NetworkManager.ts +2 -2
  5. package/front_end/core/sdk/OverlayModel.ts +11 -12
  6. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +84 -32
  7. package/front_end/models/emulation/DeviceModeModel.ts +1 -1
  8. package/front_end/models/issues_manager/CookieIssue.ts +0 -13
  9. package/front_end/models/source_map_scopes/NamesResolver.ts +11 -3
  10. package/front_end/models/source_map_scopes/ScopeChainModel.ts +10 -6
  11. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +281 -3
  12. package/front_end/panels/accessibility/accessibilityAnnouncementRecordingView.css +85 -0
  13. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +1 -2
  14. package/front_end/panels/animation/AnimationTimeline.ts +1 -1
  15. package/front_end/panels/application/CrashReportContextView.ts +1 -1
  16. package/front_end/panels/application/IndexedDBViews.ts +1 -1
  17. package/front_end/panels/application/KeyValueStorageItemsView.ts +1 -0
  18. package/front_end/panels/application/components/AdsView.ts +63 -1
  19. package/front_end/panels/application/components/adsView.css +11 -1
  20. package/front_end/panels/common/CommentThreadWidget.ts +1 -16
  21. package/front_end/panels/common/commentThreadWidget.css +0 -12
  22. package/front_end/panels/elements/CSSRuleValidator.ts +1 -1
  23. package/front_end/panels/elements/ElementsPanel.ts +4 -4
  24. package/front_end/panels/elements/ElementsTreeElement.ts +7 -2
  25. package/front_end/panels/elements/ElementsTreeOutline.ts +14 -26
  26. package/front_end/panels/elements/elementsTreeOutline.css +4 -4
  27. package/front_end/panels/lighthouse/LighthouseController.ts +1 -1
  28. package/front_end/panels/mobile_throttling/NetworkPanelIndicator.ts +1 -1
  29. package/front_end/panels/mobile_throttling/ThrottlingManager.ts +1 -1
  30. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +1 -1
  31. package/front_end/panels/recorder/StepView.ts +19 -4
  32. package/front_end/panels/recorder/TimelineSection.ts +1 -1
  33. package/front_end/panels/settings/BackendLinkingSettingsTab.ts +2 -1
  34. package/front_end/panels/settings/EditFileSystemView.ts +1 -1
  35. package/front_end/panels/sources/CallStackSidebarPane.ts +1 -1
  36. package/front_end/panels/sources/DebuggerPlugin.ts +1 -1
  37. package/front_end/panels/timeline/components/DetailsView.ts +3 -3
  38. package/front_end/panels/timeline/components/OriginMap.ts +1 -1
  39. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -3
  40. package/front_end/third_party/chromium/README.chromium +1 -1
  41. package/front_end/ui/components/buttons/Button.ts +19 -1
  42. package/front_end/ui/legacy/ContextMenu.ts +16 -1
  43. package/front_end/ui/legacy/PlusButton.ts +5 -6
  44. package/front_end/ui/legacy/Treeoutline.ts +2 -0
  45. package/front_end/ui/legacy/Widget.ts +33 -0
  46. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +3 -0
  47. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +14 -4
  48. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +22 -25
  49. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  50. package/package.json +1 -1
  51. package/.agents/skills/devtools-ci/SKILL.md +0 -65
  52. package/.agents/skills/evaluate-ai-css-completion/SKILL.md +0 -143
  53. package/.agents/skills/evaluate-ai-css-completion/scripts/evaluate.js +0 -104
  54. package/.agents/skills/fixing-skipped-tests/SKILL.md +0 -32
  55. package/.agents/skills/gerrit-cli/SKILL.md +0 -74
  56. package/.agents/skills/gerrit-cli/references/commands.md +0 -169
  57. package/.agents/skills/gerrit-cli/references/workflows.md +0 -63
  58. package/.agents/skills/gerrit-cli/scripts/import_upstream.py +0 -85
  59. package/front_end/models/issues_manager/descriptions/cookieExcludeBlockedWithinRelatedWebsiteSet.md +0 -4
@@ -6,25 +6,192 @@ import type * as Common from '../../core/common/common.js';
6
6
  import * as i18n from '../../core/i18n/i18n.js';
7
7
  import * as SDK from '../../core/sdk/sdk.js';
8
8
  import type * as Protocol from '../../generated/protocol.js';
9
+ import * as Buttons from '../../ui/components/buttons/buttons.js';
10
+ import * as UI from '../../ui/legacy/legacy.js';
11
+ import * as Lit from '../../ui/lit/lit.js';
12
+ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
9
13
 
14
+ import {AccessibilityAnnouncementRecordingListView} from './AccessibilityAnnouncementRecordingListView.js';
15
+ import accessibilityAnnouncementRecordingViewStyles from './accessibilityAnnouncementRecordingView.css.js';
10
16
  import {AccessibilitySubPane} from './AccessibilitySubPane.js';
11
17
 
18
+ const {html, render} = Lit;
19
+ const {widget} = UI.Widget;
20
+
12
21
  const UIStrings = {
13
22
  /**
14
23
  * @description Title for the ARIA-Live and JS announcements recording tool
15
24
  */
16
25
  ariaLiveRecording: 'Announcements recording',
26
+ /**
27
+ * @description Tooltip for the start recording button in the announcements tool.
28
+ */
29
+ startRecording: 'Start recording',
30
+ /**
31
+ * @description Tooltip for the stop recording button in the announcements tool.
32
+ */
33
+ stopRecording: 'Stop recording',
34
+ /**
35
+ * @description Tooltip for the clear announcements button in the announcements tool.
36
+ */
37
+ clearAnnouncements: 'Clear announcements',
38
+ /**
39
+ * @description Label/title for the dropdown filter to select which announcement types to record.
40
+ */
41
+ filterByType: 'Filter by type',
42
+ /**
43
+ * @description Option label to record and display both ARIA-live and JavaScript announcements.
44
+ */
45
+ recordBoth: 'Record both',
46
+ /**
47
+ * @description Option label to record and display only ARIA-live announcements.
48
+ */
49
+ ariaLiveOnly: 'ARIA-live only',
50
+ /**
51
+ * @description Option label to record and display only JavaScript-triggered announcements.
52
+ */
53
+ announcementsOnly: 'Announcements only',
54
+ /**
55
+ * @description Placeholder text for the filter input in the announcements tool.
56
+ */
57
+ filter: 'Filter',
58
+ /**
59
+ * @description Screen reader announcement when no events match the filter in the announcements tool.
60
+ */
61
+ noEventsMatch: 'No events match',
62
+ /**
63
+ * @description Screen reader announcement when exactly one event matches the filter in the announcements tool.
64
+ */
65
+ oneEventMatches: '1 event matches',
66
+ /**
67
+ * @description Screen reader announcement when multiple events match the filter in the announcements tool.
68
+ * @example {15} PH1
69
+ */
70
+ nEventsMatch: '{PH1} events match',
71
+ /**
72
+ * @description Warning banner title shown when recording could not be enabled in some frames.
73
+ */
74
+ recordingBlockedWarning: 'Recording was blocked for some frames:',
75
+ /**
76
+ * @description Warning item describing a specific frame and the reason recording was blocked.
77
+ * @example {iframe#main} PH1
78
+ * @example {Script evaluation failed} PH2
79
+ */
80
+ frameBlockedReason: '{PH1}: {PH2}',
81
+ /**
82
+ * @description Fallback reason shown when an unknown error occurs while blocking recording.
83
+ */
84
+ unknownError: 'Unknown error',
17
85
  } as const;
18
86
  const str_ = i18n.i18n.registerUIStrings('panels/accessibility/AccessibilityAnnouncementRecordingView.ts', UIStrings);
19
87
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
20
88
 
21
89
  export const BINDING_NAME = '__announcementsRecorderBinding';
22
90
 
23
- export const enum AnnouncementApi {
91
+ export enum AnnouncementApi {
24
92
  ARIA_LIVE = 'aria-live',
25
93
  JS_TRIGGERED = 'js-triggered',
26
94
  }
27
95
 
96
+ export const enum RecordTypeFilter {
97
+ BOTH = 'both',
98
+ ARIA_LIVE = 'aria-live',
99
+ JS_TRIGGERED = 'js-triggered',
100
+ }
101
+
102
+ export interface BlockedTargetInfo {
103
+ targetName: string;
104
+ reason: string;
105
+ }
106
+
107
+ export interface ViewInput {
108
+ isRecording: boolean;
109
+ onToggleRecording: () => void;
110
+ onClear: () => void;
111
+ recordTypeFilter: RecordTypeFilter;
112
+ onRecordTypeFilterChange: (type: RecordTypeFilter) => void;
113
+ textFilter: string;
114
+ onTextFilterChange: (text: string) => void;
115
+ blockedTargets: BlockedTargetInfo[];
116
+ announcements: readonly A11yAnnouncement[];
117
+ }
118
+
119
+ export type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;
120
+
121
+ export const DEFAULT_VIEW: View = (input, _output, target) => {
122
+ // clang-format off
123
+ render(html`
124
+ <style>${accessibilityAnnouncementRecordingViewStyles}</style>
125
+ <div class="accessibility-announcement-recording-view">
126
+ <div class="announcements-toolbar-container">
127
+ <devtools-toolbar class="announcements-toolbar" jslog=${VisualLogging.toolbar()}>
128
+ <devtools-button
129
+ title=${input.isRecording ? i18nString(UIStrings.stopRecording) : i18nString(UIStrings.startRecording)}
130
+ aria-label=${input.isRecording ? i18nString(UIStrings.stopRecording) : i18nString(UIStrings.startRecording)}
131
+ .iconName=${'record-start'}
132
+ .toggledIconName=${'record-stop'}
133
+ .toggleType=${Buttons.Button.ToggleType.PRIMARY}
134
+ .toggled=${input.isRecording}
135
+ @click=${input.onToggleRecording}
136
+ .variant=${Buttons.Button.Variant.TOOLBAR}
137
+ .jslogContext=${'accessibility.toggle-recording'}>
138
+ </devtools-button>
139
+ <devtools-button
140
+ title=${i18nString(UIStrings.clearAnnouncements)}
141
+ aria-label=${i18nString(UIStrings.clearAnnouncements)}
142
+ .iconName=${'clear'}
143
+ @click=${input.onClear}
144
+ .variant=${Buttons.Button.Variant.TOOLBAR}
145
+ .jslogContext=${'accessibility.clear-announcements'}>
146
+ </devtools-button>
147
+ <div class="toolbar-divider" role="separator"></div>
148
+ <select
149
+ title=${i18nString(UIStrings.filterByType)}
150
+ aria-label=${i18nString(UIStrings.filterByType)}
151
+ @change=${(event: Event) => input.onRecordTypeFilterChange((event.target as HTMLSelectElement).value as RecordTypeFilter)}
152
+ .value=${input.recordTypeFilter}
153
+ jslog=${VisualLogging.dropDown('accessibility-announcements.filter-by-type').track({change: true})}>
154
+ <option value=${RecordTypeFilter.BOTH} .selected=${input.recordTypeFilter === RecordTypeFilter.BOTH}>
155
+ ${i18nString(UIStrings.recordBoth)}
156
+ </option>
157
+ <option value=${RecordTypeFilter.ARIA_LIVE} .selected=${input.recordTypeFilter === RecordTypeFilter.ARIA_LIVE}>
158
+ ${i18nString(UIStrings.ariaLiveOnly)}
159
+ </option>
160
+ <option value=${RecordTypeFilter.JS_TRIGGERED} .selected=${input.recordTypeFilter === RecordTypeFilter.JS_TRIGGERED}>
161
+ ${i18nString(UIStrings.announcementsOnly)}
162
+ </option>
163
+ </select>
164
+ <div class="toolbar-divider" role="separator"></div>
165
+ <devtools-toolbar-input
166
+ type="filter"
167
+ placeholder=${i18nString(UIStrings.filter)}
168
+ .value=${input.textFilter}
169
+ @change=${(event: CustomEvent<string>) => input.onTextFilterChange(event.detail)}
170
+ style="flex-grow: 1">
171
+ </devtools-toolbar-input>
172
+ </devtools-toolbar>
173
+ </div>
174
+ ${input.blockedTargets.length > 0 ? html`
175
+ <div class="announcements-blocked-banner" role="alert">
176
+ <div class="blocked-banner-header">
177
+ <devtools-icon name="warning-filled"></devtools-icon>
178
+ <span>${i18nString(UIStrings.recordingBlockedWarning)}</span>
179
+ </div>
180
+ <ul class="blocked-targets-list">
181
+ ${input.blockedTargets.map(targetInfo => html`
182
+ <li>${i18nString(UIStrings.frameBlockedReason, {PH1: targetInfo.targetName, PH2: targetInfo.reason || i18nString(UIStrings.unknownError)})}</li>
183
+ `)}
184
+ </ul>
185
+ </div>
186
+ ` : Lit.nothing}
187
+ <div class="announcements-main-pane">
188
+ ${widget(AccessibilityAnnouncementRecordingListView, {items: input.announcements})}
189
+ </div>
190
+ </div>`,
191
+ target);
192
+ // clang-format on
193
+ };
194
+
28
195
  declare global {
29
196
  interface Window {
30
197
  // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -564,17 +731,23 @@ export function validateAndSanitizeAnnouncement(payload: unknown): A11yAnnouncem
564
731
 
565
732
  export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane implements SDK.TargetManager.Observer {
566
733
  #announcements: A11yAnnouncement[] = [];
734
+ #filteredAnnouncements: readonly A11yAnnouncement[]|null = null;
567
735
  #isRecording = false;
568
736
  #blockedTargets = new Map<SDK.Target.Target, string>();
569
737
  #scriptIdentifiers = new Map<SDK.Target.Target, Protocol.Page.ScriptIdentifier>();
570
738
  #targets = new Set<SDK.Target.Target>();
571
739
  #enabledTargets = new Set<SDK.Target.Target>();
740
+ #recordTypeFilter: RecordTypeFilter = RecordTypeFilter.BOTH;
741
+ #textFilter = '';
742
+ #regexFilter: RegExp|null = null;
743
+ readonly #view: View;
572
744
 
573
- constructor() {
745
+ constructor(view: View = DEFAULT_VIEW) {
574
746
  super({
575
747
  title: i18nString(UIStrings.ariaLiveRecording),
576
748
  viewId: 'aria-live-recording',
577
749
  });
750
+ this.#view = view;
578
751
  SDK.TargetManager.TargetManager.instance().observeTargets(this, {scoped: true});
579
752
  }
580
753
 
@@ -595,8 +768,11 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
595
768
 
596
769
  async targetRemoved(target: SDK.Target.Target): Promise<void> {
597
770
  this.#targets.delete(target);
598
- this.#blockedTargets.delete(target);
771
+ const wasBlocked = this.#blockedTargets.delete(target);
599
772
  await this.#disableTarget(target);
773
+ if (wasBlocked) {
774
+ this.requestUpdate();
775
+ }
600
776
  }
601
777
 
602
778
  async #enableTarget(target: SDK.Target.Target): Promise<void> {
@@ -708,6 +884,11 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
708
884
  return;
709
885
  }
710
886
  this.#announcements.push(announcement);
887
+ if (this.#filteredAnnouncements !== null) {
888
+ if (this.#matchesFilter(announcement)) {
889
+ this.#filteredAnnouncements = [...this.#filteredAnnouncements, announcement];
890
+ }
891
+ }
711
892
  this.requestUpdate();
712
893
  }
713
894
 
@@ -736,9 +917,106 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
736
917
 
737
918
  clearAnnouncements(): void {
738
919
  this.#announcements = [];
920
+ this.#filteredAnnouncements = [];
921
+ this.requestUpdate();
922
+ }
923
+
924
+ #matchesFilter(announcement: A11yAnnouncement): boolean {
925
+ if (this.#recordTypeFilter === RecordTypeFilter.ARIA_LIVE && announcement.api !== AnnouncementApi.ARIA_LIVE) {
926
+ return false;
927
+ }
928
+ if (this.#recordTypeFilter === RecordTypeFilter.JS_TRIGGERED && announcement.api !== AnnouncementApi.JS_TRIGGERED) {
929
+ return false;
930
+ }
931
+ if (this.#regexFilter && !this.#regexFilter.test(announcement.message)) {
932
+ return false;
933
+ }
934
+ return true;
935
+ }
936
+
937
+ get filteredAnnouncements(): readonly A11yAnnouncement[] {
938
+ if (this.#filteredAnnouncements !== null) {
939
+ return this.#filteredAnnouncements;
940
+ }
941
+ this.#filteredAnnouncements = this.#announcements.filter(announcement => this.#matchesFilter(announcement));
942
+ return this.#filteredAnnouncements;
943
+ }
944
+
945
+ #announceFilterMatches(): void {
946
+ const count = this.filteredAnnouncements.length;
947
+ let message: string;
948
+ if (count === 0) {
949
+ message = i18nString(UIStrings.noEventsMatch);
950
+ } else if (count === 1) {
951
+ message = i18nString(UIStrings.oneEventMatches);
952
+ } else {
953
+ message = i18nString(UIStrings.nEventsMatch, {PH1: count});
954
+ }
955
+ UI.ARIAUtils.LiveAnnouncer.alert(message);
956
+ }
957
+
958
+ setRecordTypeFilter(type: RecordTypeFilter): void {
959
+ if (this.#recordTypeFilter === type) {
960
+ return;
961
+ }
962
+ this.#recordTypeFilter = type;
963
+ this.#filteredAnnouncements = null;
964
+ this.#announceFilterMatches();
739
965
  this.requestUpdate();
740
966
  }
741
967
 
968
+ setTextFilter(text: string): void {
969
+ if (this.#textFilter === text) {
970
+ return;
971
+ }
972
+ this.#textFilter = text;
973
+ if (!text) {
974
+ this.#regexFilter = null;
975
+ } else {
976
+ try {
977
+ this.#regexFilter = new RegExp(text, 'i');
978
+ } catch {
979
+ this.#regexFilter = new RegExp('(?!)', 'i');
980
+ }
981
+ }
982
+ this.#filteredAnnouncements = null;
983
+ this.#announceFilterMatches();
984
+ this.requestUpdate();
985
+ }
986
+
987
+ override performUpdate(): void {
988
+ const blockedTargets: BlockedTargetInfo[] = [];
989
+ for (const [target, reason] of this.#blockedTargets) {
990
+ const targetName = target.name() || target.inspectedURL() || target.id();
991
+ blockedTargets.push({targetName, reason});
992
+ }
993
+
994
+ const input: ViewInput = {
995
+ isRecording: this.#isRecording,
996
+ onToggleRecording: () => {
997
+ if (this.#isRecording) {
998
+ void this.stopRecording();
999
+ } else {
1000
+ void this.startRecording();
1001
+ }
1002
+ },
1003
+ onClear: () => {
1004
+ this.clearAnnouncements();
1005
+ },
1006
+ recordTypeFilter: this.#recordTypeFilter,
1007
+ onRecordTypeFilterChange: (type: RecordTypeFilter) => {
1008
+ this.setRecordTypeFilter(type);
1009
+ },
1010
+ textFilter: this.#textFilter,
1011
+ onTextFilterChange: (text: string) => {
1012
+ this.setTextFilter(text);
1013
+ },
1014
+ blockedTargets,
1015
+ announcements: this.filteredAnnouncements,
1016
+ };
1017
+ this.#view(input, undefined, this.contentElement);
1018
+ }
1019
+
742
1020
  announcementsForTest(): A11yAnnouncement[] {
743
1021
  return [...this.#announcements];
744
1022
  }
@@ -0,0 +1,85 @@
1
+ /*
2
+ * Copyright 2026 The Chromium Authors
3
+ * Use of this source code is governed by a BSD-style license that can be
4
+ * found in the LICENSE file.
5
+ */
6
+
7
+ @scope to (devtools-widget > *) {
8
+ :scope {
9
+ font-family: inherit;
10
+ display: flex;
11
+ flex-direction: column;
12
+ flex: auto;
13
+ height: 100%;
14
+ overflow: hidden;
15
+ }
16
+
17
+ .accessibility-announcement-recording-view {
18
+ display: flex;
19
+ flex-direction: column;
20
+ flex: auto;
21
+ height: 100%;
22
+ overflow: hidden;
23
+ }
24
+
25
+ .announcements-toolbar-container {
26
+ display: flex;
27
+ flex: none;
28
+ background-color: var(--sys-color-cdt-base-container);
29
+ border-bottom: var(--sys-size-1) solid var(--sys-color-divider);
30
+ }
31
+
32
+ .announcements-toolbar {
33
+ flex: auto;
34
+ }
35
+
36
+ .announcements-toolbar select {
37
+ background-color: transparent;
38
+ border: none;
39
+ color: var(--sys-color-on-surface);
40
+ font-size: var(--sys-typescale-body4-size);
41
+ padding: var(--sys-size-2) var(--sys-size-4);
42
+ height: 100%;
43
+ cursor: pointer;
44
+ }
45
+
46
+ .toolbar-divider {
47
+ background-color: var(--sys-color-divider);
48
+ width: var(--sys-size-1);
49
+ margin: 5px var(--sys-size-3);
50
+ height: var(--sys-size-8);
51
+ }
52
+
53
+ .announcements-blocked-banner {
54
+ display: flex;
55
+ flex-direction: column;
56
+ flex: none;
57
+ gap: var(--sys-size-3);
58
+ padding: var(--sys-size-5) var(--sys-size-8);
59
+ background-color: var(--sys-color-surface-yellow);
60
+ color: var(--sys-color-on-surface-yellow);
61
+ border-bottom: var(--sys-size-1) solid var(--sys-color-divider);
62
+ font-size: var(--sys-typescale-body4-size);
63
+ line-height: var(--sys-typescale-body4-line-height);
64
+ }
65
+
66
+ .blocked-banner-header {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: var(--sys-size-4);
70
+ font-weight: var(--ref-typeface-weight-medium);
71
+ }
72
+
73
+ .blocked-targets-list {
74
+ margin: 0;
75
+ padding-left: var(--sys-size-8);
76
+ }
77
+
78
+ .announcements-main-pane {
79
+ display: flex;
80
+ flex-direction: column;
81
+ flex: auto;
82
+ min-height: 0;
83
+ overflow: hidden;
84
+ }
85
+ }
@@ -391,9 +391,8 @@ function toolbarView(input: ToolbarViewInput): Lit.LitTemplate {
391
391
  @click=${input.onNewChatClick}></devtools-button>
392
392
  <div class="toolbar-divider"></div>
393
393
  <devtools-menu-button
394
- title=${i18nString(UIStrings.history)}
395
- aria-label=${i18nString(UIStrings.history)}
396
394
  .iconName=${'history'}
395
+ .accessibleLabel=${i18nString(UIStrings.history)}
397
396
  .jslogContext=${'freestyler.history'}
398
397
  .populateMenuCall=${input.populateHistoryMenu}
399
398
  ></devtools-menu-button>`
@@ -72,7 +72,7 @@ const UIStrings = {
72
72
  /**
73
73
  * @description Empty buffer hint text content in the animation timeline of the Animations panel that explains the panel.
74
74
  */
75
- animationDescription: 'On this page you can inspect and modify animations.',
75
+ animationDescription: 'On this page you can inspect and modify animations',
76
76
  /**
77
77
  * @description Tooltip text that appears when hovering over largeicon replay animation button in the animation timeline of the Animations panel.
78
78
  */
@@ -20,7 +20,7 @@ const UIStrings = {
20
20
  /**
21
21
  * @description Placeholder text when no context is detected.
22
22
  */
23
- noContext: 'No context entries detected across frames.',
23
+ noContext: 'No context entries detected across frames',
24
24
  /**
25
25
  * @description Fallback label when a frame has no URL.
26
26
  */
@@ -289,7 +289,7 @@ const populateContextMenu = (e: CustomEvent<UI.ContextMenu.ContextMenu>): void =
289
289
  const renderDataGrid = (input: IndexedDBDataViewInput): LitTemplate => {
290
290
  const keyPath = input.isIndex && input.index ? input.index.keyPath : input.objectStore.keyPath;
291
291
  // clang-format off
292
- return html`<devtools-data-grid row-height="auto" striped style="flex: auto;" name=${i18nString(UIStrings.indexedDb)} .template=${html`
292
+ return html`<devtools-data-grid row-height="auto" striped deletable style="flex: auto;" name=${i18nString(UIStrings.indexedDb)} .template=${html`
293
293
  <style>${indexedDBViewsStyles}</style>
294
294
  <table>
295
295
  <tr>
@@ -153,6 +153,7 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
153
153
  <devtools-data-grid
154
154
  .name=${`${id}-datagrid-with-preview`}
155
155
  striped
156
+ deletable
156
157
  style="flex: auto"
157
158
  @sort=${(e: CustomEvent<{columnId: string, ascending: boolean}>) => input.onSort(e.detail.ascending)}
158
159
  @refresh=${input.onRefresh}
@@ -23,6 +23,12 @@ const {html} = Lit;
23
23
  const {repeat} = Lit.Directives;
24
24
  const {bindToSetting} = UI.UIUtils;
25
25
 
26
+ const DENSITY_DOC_URL = 'https://developer.chrome.com/docs/ads/metrics/density';
27
+ const COUNT_DOC_URL = 'https://developer.chrome.com/docs/ads/metrics/count';
28
+ const CPU_USAGE_DOC_URL = 'https://developer.chrome.com/docs/ads/metrics/weight-cpu';
29
+ const NETWORK_USAGE_DOC_URL = 'https://developer.chrome.com/docs/ads/metrics/weight-network';
30
+ const AD_DETECTION_DOC_URL = 'https://developer.chrome.com/docs/ads/detection';
31
+
26
32
  const UIStrings = {
27
33
  /**
28
34
  * @description Title for the metrics table.
@@ -32,18 +38,34 @@ const UIStrings = {
32
38
  * @description Title for a metric showing the percentage of the viewport covered by ads.
33
39
  */
34
40
  viewportAdDensity: 'Viewport ad density',
41
+ /**
42
+ * @description Tooltip text explaining the viewport ad density metric in the ads view of the Application panel.
43
+ */
44
+ viewportAdDensityExplanation: 'Percentage of the viewport covered by ads',
35
45
  /**
36
46
  * @description Title for a metric showing the number of ads in the viewport.
37
47
  */
38
48
  viewportAdCount: 'Viewport ad count',
49
+ /**
50
+ * @description Tooltip text explaining the viewport ad count metric in the ads view of the Application panel.
51
+ */
52
+ viewportAdCountExplanation: 'Number of ads in the viewport',
39
53
  /**
40
54
  * @description Title for a metric showing the total CPU usage by ads.
41
55
  */
42
56
  totalCpuUsage: 'Total CPU usage by ads',
57
+ /**
58
+ * @description Tooltip text explaining the total CPU usage metric in the ads view of the Application panel.
59
+ */
60
+ totalCpuUsageExplanation: 'Total CPU time consumed by ads',
43
61
  /**
44
62
  * @description Title for a metric showing the total network usage by ads.
45
63
  */
46
64
  totalNetworkUsage: 'Total network usage by ads',
65
+ /**
66
+ * @description Tooltip text explaining the total network usage metric in the ads view of the Application panel.
67
+ */
68
+ totalNetworkUsageExplanation: 'Total network data consumed by ads',
47
69
  /**
48
70
  * @description Subtext showing the average value of a metric.
49
71
  * @example {5.00%} PH1
@@ -231,6 +253,26 @@ const DEFAULT_VIEW: View = (input, output, target) => {
231
253
  });
232
254
  };
233
255
 
256
+ const renderMetricTooltip = (tooltipId: string, explanation: string, url: string): Lit.LitTemplate => {
257
+ return html`
258
+ <devtools-icon
259
+ name="info"
260
+ class="small metric-info-icon"
261
+ tabindex="0"
262
+ role="button"
263
+ aria-details=${tooltipId}
264
+ aria-label=${explanation}
265
+ ></devtools-icon>
266
+ <devtools-tooltip id=${tooltipId} variant="rich" prefer-span-left @copy=${stopPropagation}>
267
+ <span>${explanation}</span>
268
+ &#32;
269
+ <devtools-link href=${url} jslogcontext="learn-more">
270
+ ${i18nString(UIStrings.learnMore)}
271
+ </devtools-link>
272
+ </devtools-tooltip>
273
+ `;
274
+ };
275
+
234
276
  // clang-format off
235
277
  Lit.render(html`
236
278
  <style>${adsViewStyles}</style>
@@ -245,6 +287,11 @@ const DEFAULT_VIEW: View = (input, output, target) => {
245
287
  PH1: formatAverage(metrics.averageViewportAdDensityByArea, true),
246
288
  })}</span>
247
289
  </dd>
290
+ ${renderMetricTooltip(
291
+ 'density-metric-tooltip',
292
+ i18nString(UIStrings.viewportAdDensityExplanation),
293
+ DENSITY_DOC_URL,
294
+ )}
248
295
  </div>
249
296
  <div class="metric-box">
250
297
  <dt class="metric-title">${i18nString(UIStrings.viewportAdCount)}</dt>
@@ -254,18 +301,33 @@ const DEFAULT_VIEW: View = (input, output, target) => {
254
301
  PH1: formatAverage(metrics.averageViewportAdCount, false),
255
302
  })}</span>
256
303
  </dd>
304
+ ${renderMetricTooltip(
305
+ 'count-metric-tooltip',
306
+ i18nString(UIStrings.viewportAdCountExplanation),
307
+ COUNT_DOC_URL,
308
+ )}
257
309
  </div>
258
310
  <div class="metric-box">
259
311
  <dt class="metric-title">${i18nString(UIStrings.totalCpuUsage)}</dt>
260
312
  <dd class="metric-value">
261
313
  <span>${formatCpu(metrics.totalAdCpuTime)}</span>
262
314
  </dd>
315
+ ${renderMetricTooltip(
316
+ 'cpu-metric-tooltip',
317
+ i18nString(UIStrings.totalCpuUsageExplanation),
318
+ CPU_USAGE_DOC_URL,
319
+ )}
263
320
  </div>
264
321
  <div class="metric-box">
265
322
  <dt class="metric-title">${i18nString(UIStrings.totalNetworkUsage)}</dt>
266
323
  <dd class="metric-value">
267
324
  <span>${formatNetwork(metrics.totalAdNetworkBytes)}</span>
268
325
  </dd>
326
+ ${renderMetricTooltip(
327
+ 'network-metric-tooltip',
328
+ i18nString(UIStrings.totalNetworkUsageExplanation),
329
+ NETWORK_USAGE_DOC_URL,
330
+ )}
269
331
  </div>
270
332
  </dl>
271
333
  <hr class="divider">
@@ -374,7 +436,7 @@ const DEFAULT_VIEW: View = (input, output, target) => {
374
436
  <span>
375
437
  ${i18nString(UIStrings.adDetectionMistakes)}
376
438
  &#32;
377
- <devtools-link class="link devtools-link" href="https://chromium.googlesource.com/chromium/src/+/main/docs/ad_tagging.md" jslogcontext="learn-more">
439
+ <devtools-link href=${AD_DETECTION_DOC_URL} jslogcontext="learn-more">
378
440
  ${i18nString(UIStrings.learnMore)}
379
441
  </devtools-link>
380
442
  </span>
@@ -29,17 +29,20 @@
29
29
 
30
30
  .metric-box {
31
31
  background-color: var(--sys-color-surface);
32
- padding: var(--sys-size-6);
32
+ padding: var(--sys-size-4);
33
33
  display: flex;
34
34
  flex-direction: column;
35
35
  align-items: center;
36
36
  justify-content: center;
37
+ position: relative;
37
38
  }
38
39
 
39
40
  .metric-title {
40
41
  font-size: var(--sys-typescale-body4-size);
41
42
  color: var(--sys-color-on-surface-subtle);
42
43
  margin: 0 0 var(--sys-size-3);
44
+ padding: 0 var(--sys-size-8);
45
+ text-align: center;
43
46
  }
44
47
 
45
48
  .metric-value {
@@ -59,6 +62,13 @@
59
62
  color: var(--sys-color-on-surface-subtle);
60
63
  }
61
64
 
65
+ .metric-info-icon {
66
+ position: absolute;
67
+ top: var(--sys-size-4);
68
+ right: var(--sys-size-4);
69
+ cursor: pointer;
70
+ }
71
+
62
72
  .metrics-title,
63
73
  .ad-frames-title,
64
74
  .ad-scripts-title,