chrome-devtools-frontend 1.0.1515988 → 1.0.1518653

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 (122) hide show
  1. package/docs/checklist/README.md +2 -2
  2. package/docs/checklist/javascript.md +1 -1
  3. package/docs/contributing/README.md +1 -1
  4. package/docs/contributing/settings-experiments-features.md +9 -8
  5. package/docs/cookbook/devtools_on_devtools.md +2 -2
  6. package/docs/cookbook/localization.md +10 -10
  7. package/docs/devtools-protocol.md +9 -8
  8. package/docs/ecosystem/automatic_workspace_folders.md +3 -3
  9. package/docs/get_the_code.md +0 -2
  10. package/docs/styleguide/ux/components.md +166 -85
  11. package/docs/styleguide/ux/numbers.md +3 -4
  12. package/front_end/core/common/README.md +13 -12
  13. package/front_end/core/host/GdpClient.ts +16 -1
  14. package/front_end/core/host/UserMetrics.ts +8 -2
  15. package/front_end/core/root/Runtime.ts +13 -0
  16. package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
  17. package/front_end/entrypoints/main/MainImpl.ts +6 -3
  18. package/front_end/generated/InspectorBackendCommands.js +10 -7
  19. package/front_end/generated/SupportedCSSProperties.js +21 -7
  20. package/front_end/generated/protocol-mapping.d.ts +16 -1
  21. package/front_end/generated/protocol-proxy-api.d.ts +13 -1
  22. package/front_end/generated/protocol.ts +95 -0
  23. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +170 -54
  24. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
  25. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +13 -315
  26. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +224 -50
  27. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +310 -11
  28. package/front_end/models/ai_assistance/performance/AIContext.ts +15 -2
  29. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +41 -19
  30. package/front_end/models/badges/Badge.ts +8 -3
  31. package/front_end/models/badges/CodeWhispererBadge.ts +2 -4
  32. package/front_end/models/badges/StarterBadge.ts +2 -2
  33. package/front_end/models/badges/UserBadges.ts +59 -6
  34. package/front_end/models/formatter/FormatterWorkerPool.ts +3 -3
  35. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
  36. package/front_end/models/trace/README.md +28 -1
  37. package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
  38. package/front_end/models/trace/helpers/Trace.ts +99 -43
  39. package/front_end/models/trace/types/TraceEvents.ts +9 -0
  40. package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
  41. package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
  42. package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
  43. package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
  44. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +16 -2
  45. package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
  46. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +42 -0
  47. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
  48. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
  49. package/front_end/panels/common/BadgeNotification.ts +67 -15
  50. package/front_end/panels/common/GdpSignUpDialog.ts +18 -9
  51. package/front_end/panels/console/ConsolePrompt.ts +1 -1
  52. package/front_end/panels/console/ConsoleView.ts +6 -2
  53. package/front_end/panels/elements/ComputedStyleWidget.ts +1 -2
  54. package/front_end/panels/elements/ElementsPanel.ts +4 -0
  55. package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
  56. package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
  57. package/front_end/panels/elements/LayoutPane.ts +1 -1
  58. package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
  59. package/front_end/panels/media/TickingFlameChart.ts +1 -1
  60. package/front_end/panels/network/NetworkLogView.ts +5 -1
  61. package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
  62. package/front_end/panels/search/SearchResultsPane.ts +126 -145
  63. package/front_end/panels/search/SearchView.ts +43 -59
  64. package/front_end/panels/settings/components/SyncSection.ts +16 -8
  65. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -1
  66. package/front_end/panels/sources/OutlineQuickOpen.ts +3 -1
  67. package/front_end/panels/sources/SourcesPanel.ts +3 -0
  68. package/front_end/panels/timeline/AppenderUtils.ts +2 -2
  69. package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
  70. package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
  71. package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
  72. package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
  73. package/front_end/panels/timeline/ThreadAppender.ts +12 -3
  74. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
  75. package/front_end/panels/timeline/TimelinePanel.ts +3 -2
  76. package/front_end/panels/timeline/TimelineUIUtils.ts +18 -12
  77. package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
  78. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
  79. package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
  80. package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
  81. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
  82. package/front_end/third_party/chromium/README.chromium +1 -1
  83. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  84. package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
  85. package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
  86. package/front_end/third_party/codemirror.next/package.json +2 -1
  87. package/front_end/third_party/diff/README.chromium +1 -0
  88. package/front_end/third_party/puppeteer/README.chromium +2 -2
  89. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
  90. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Accessibility.js +0 -20
  91. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Accessibility.js.map +1 -1
  92. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
  93. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +1 -1
  96. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  98. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  99. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +2 -23
  100. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Accessibility.js +0 -20
  101. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Accessibility.js.map +1 -1
  102. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
  104. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +1 -1
  105. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  107. package/front_end/third_party/puppeteer/package/package.json +1 -1
  108. package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +1 -21
  109. package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
  110. package/front_end/third_party/puppeteer/package/src/revisions.ts +1 -1
  111. package/front_end/ui/components/text_editor/config.ts +36 -8
  112. package/front_end/ui/components/tooltips/Tooltip.ts +71 -34
  113. package/front_end/ui/legacy/README.md +33 -24
  114. package/front_end/ui/legacy/SearchableView.ts +19 -26
  115. package/front_end/ui/legacy/TextPrompt.ts +166 -1
  116. package/front_end/ui/legacy/Treeoutline.ts +16 -2
  117. package/front_end/ui/legacy/UIUtils.ts +15 -2
  118. package/front_end/ui/legacy/XElement.ts +0 -43
  119. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
  120. package/front_end/ui/visual_logging/KnownContextValues.ts +24 -6
  121. package/front_end/ui/visual_logging/README.md +43 -27
  122. package/package.json +1 -1
@@ -33,6 +33,10 @@ const UIStringsNotTranslate = {
33
33
  */
34
34
  tooltipDisclaimerTextForAiCodeCompletionNoLogging:
35
35
  'To generate code suggestions, your console input and the history of your current console session are shared with Google. This data will not be used to improve Google’s AI models.',
36
+ /**
37
+ * Text for tooltip shown on hovering over spinner.
38
+ */
39
+ tooltipTextForSpinner: 'Shows when data is being sent to Google to generate code suggestions',
36
40
  /**
37
41
  * @description Text for tooltip button which redirects to AI settings
38
42
  */
@@ -47,6 +51,7 @@ const lockedString = i18n.i18n.lockedString;
47
51
 
48
52
  export interface ViewInput {
49
53
  disclaimerTooltipId?: string;
54
+ spinnerTooltipId?: string;
50
55
  noLogging: boolean;
51
56
  aidaAvailability?: Host.AidaClient.AidaAccessPreconditions;
52
57
  onManageInSettingsTooltipClick: () => void;
@@ -59,12 +64,14 @@ export interface ViewOutput {
59
64
 
60
65
  export type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;
61
66
 
62
- export const DEFAULT_SUMMARY_TOOLBAR_VIEW: View = (input, output, target) => {
63
- if (input.aidaAvailability !== Host.AidaClient.AidaAccessPreconditions.AVAILABLE || !input.disclaimerTooltipId) {
64
- render(nothing, target);
65
- return;
66
- }
67
- // clang-format off
67
+ export const DEFAULT_SUMMARY_TOOLBAR_VIEW: View =
68
+ (input, output, target) => {
69
+ if (input.aidaAvailability !== Host.AidaClient.AidaAccessPreconditions.AVAILABLE || !input.disclaimerTooltipId ||
70
+ !input.spinnerTooltipId) {
71
+ render(nothing, target);
72
+ return;
73
+ }
74
+ // clang-format off
68
75
  render(
69
76
  html`
70
77
  <style>${styles}</style>
@@ -76,7 +83,16 @@ export const DEFAULT_SUMMARY_TOOLBAR_VIEW: View = (input, output, target) => {
76
83
  el.toggleAttribute('active', isLoading);
77
84
  };
78
85
  }
79
- })}></devtools-spinner>
86
+ })}
87
+ aria-details=${input.spinnerTooltipId}
88
+ aria-describedby=${input.spinnerTooltipId}></devtools-spinner>
89
+ <devtools-tooltip
90
+ id=${input.spinnerTooltipId}
91
+ variant=${'rich'}
92
+ jslogContext=${'ai-code-completion-spinner-tooltip'}>
93
+ <div class="disclaimer-tooltip-container"><div class="tooltip-text">
94
+ ${lockedString(UIStringsNotTranslate.tooltipTextForSpinner)}
95
+ </div></div></devtools-tooltip>
80
96
  <span
81
97
  tabIndex="0"
82
98
  class="link"
@@ -115,8 +131,8 @@ export const DEFAULT_SUMMARY_TOOLBAR_VIEW: View = (input, output, target) => {
115
131
  >${lockedString(UIStringsNotTranslate.manageInSettings)}</span></div></devtools-tooltip>
116
132
  </div>
117
133
  `, target);
118
- // clang-format on
119
- };
134
+ // clang-format on
135
+ };
120
136
 
121
137
  const MINIMUM_LOADING_STATE_TIMEOUT = 1000;
122
138
 
@@ -124,6 +140,7 @@ export class AiCodeCompletionDisclaimer extends UI.Widget.Widget {
124
140
  readonly #view: View;
125
141
  #viewOutput: ViewOutput = {};
126
142
 
143
+ #spinnerTooltipId?: string;
127
144
  #disclaimerTooltipId?: string;
128
145
  #noLogging: boolean; // Whether the enterprise setting is `ALLOW_WITHOUT_LOGGING` or not.
129
146
  #loading = false;
@@ -147,6 +164,11 @@ export class AiCodeCompletionDisclaimer extends UI.Widget.Widget {
147
164
  this.requestUpdate();
148
165
  }
149
166
 
167
+ set spinnerTooltipId(spinnerTooltipId: string) {
168
+ this.#spinnerTooltipId = spinnerTooltipId;
169
+ this.requestUpdate();
170
+ }
171
+
150
172
  set loading(loading: boolean) {
151
173
  if (!loading && !this.#loading) {
152
174
  return;
@@ -191,6 +213,7 @@ export class AiCodeCompletionDisclaimer extends UI.Widget.Widget {
191
213
  this.#view(
192
214
  {
193
215
  disclaimerTooltipId: this.#disclaimerTooltipId,
216
+ spinnerTooltipId: this.#spinnerTooltipId,
194
217
  noLogging: this.#noLogging,
195
218
  aidaAvailability: this.#aidaAvailability,
196
219
  onManageInSettingsTooltipClick: this.#onManageInSettingsTooltipClick.bind(this),
@@ -30,11 +30,13 @@ const lockedString = i18n.i18n.lockedString;
30
30
  export interface AiCodeCompletionSummaryToolbarProps {
31
31
  citationsTooltipId: string;
32
32
  disclaimerTooltipId?: string;
33
+ spinnerTooltipId?: string;
33
34
  hasTopBorder?: boolean;
34
35
  }
35
36
 
36
37
  export interface ViewInput {
37
38
  disclaimerTooltipId?: string;
39
+ spinnerTooltipId?: string;
38
40
  citations?: Set<string>;
39
41
  citationsTooltipId: string;
40
42
  loading: boolean;
@@ -57,10 +59,11 @@ export const DEFAULT_SUMMARY_TOOLBAR_VIEW: View = (input, _output, target) => {
57
59
  });
58
60
 
59
61
  // clang-format off
60
- const disclaimer = input.disclaimerTooltipId ?
62
+ const disclaimer = input.disclaimerTooltipId && input.spinnerTooltipId ?
61
63
  html`<devtools-widget
62
64
  .widgetConfig=${UI.Widget.widgetConfig(AiCodeCompletionDisclaimer, {
63
65
  disclaimerTooltipId: input.disclaimerTooltipId,
66
+ spinnerTooltipId: input.spinnerTooltipId,
64
67
  loading: input.loading,
65
68
  })} class="disclaimer-widget"></devtools-widget>` : nothing;
66
69
 
@@ -102,6 +105,7 @@ export class AiCodeCompletionSummaryToolbar extends UI.Widget.Widget {
102
105
  readonly #view: View;
103
106
 
104
107
  #disclaimerTooltipId?: string;
108
+ #spinnerTooltipId?: string;
105
109
  #citationsTooltipId: string;
106
110
  #citations = new Set<string>();
107
111
  #loading = false;
@@ -113,6 +117,7 @@ export class AiCodeCompletionSummaryToolbar extends UI.Widget.Widget {
113
117
  constructor(props: AiCodeCompletionSummaryToolbarProps, view?: View) {
114
118
  super();
115
119
  this.#disclaimerTooltipId = props.disclaimerTooltipId;
120
+ this.#spinnerTooltipId = props.spinnerTooltipId;
116
121
  this.#citationsTooltipId = props.citationsTooltipId;
117
122
  this.#hasTopBorder = props.hasTopBorder ?? false;
118
123
  this.#boundOnAidaAvailabilityChange = this.#onAidaAvailabilityChange.bind(this);
@@ -147,6 +152,7 @@ export class AiCodeCompletionSummaryToolbar extends UI.Widget.Widget {
147
152
  this.#view(
148
153
  {
149
154
  disclaimerTooltipId: this.#disclaimerTooltipId,
155
+ spinnerTooltipId: this.#spinnerTooltipId,
150
156
  citations: this.#citations,
151
157
  citationsTooltipId: this.#citationsTooltipId,
152
158
  loading: this.#loading,
@@ -69,21 +69,25 @@ const lockedString = i18n.i18n.lockedString;
69
69
 
70
70
  const LEFT_OFFSET = 5;
71
71
  const BOTTOM_OFFSET = 5;
72
+ const AUTO_CLOSE_TIME_IN_MS = 30000;
73
+
72
74
  export interface BadgeNotificationAction {
73
75
  label: string;
74
- jslogContext?: string;
76
+ jslogContext: string;
75
77
  title?: string;
76
78
  onClick: () => void;
77
79
  }
78
80
 
79
81
  export interface BadgeNotificationProperties {
80
82
  message: HTMLElement|string;
83
+ jslogContext: string;
81
84
  imageUri: string;
82
85
  actions: BadgeNotificationAction[];
86
+ isStarterBadge: boolean;
83
87
  }
84
88
 
85
89
  export interface ViewInput extends BadgeNotificationProperties {
86
- onCloseClick: () => void;
90
+ onDismissClick: () => void;
87
91
  }
88
92
 
89
93
  // clang-format off
@@ -101,7 +105,7 @@ const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement)
101
105
 
102
106
  const crossButton = html`<devtools-button
103
107
  class="dismiss notification-button"
104
- @click=${input.onCloseClick}
108
+ @click=${input.onDismissClick}
105
109
  jslog=${VisualLogging.action('badge-notification.dismiss').track({click: true})}
106
110
  aria-label=${i18nString(UIStrings.close)}
107
111
  .iconName=${'cross'}
@@ -112,8 +116,8 @@ const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement)
112
116
 
113
117
  render(html`
114
118
  <style>${badgeNotificationStyles}</style>
115
- <div class="container">
116
- <div class="badge-container">
119
+ <div class="container" jslog=${VisualLogging.dialog('badge-notification')}>
120
+ <div class="badge-container" jslog=${VisualLogging.item(input.jslogContext)}>
117
121
  <img class="badge-image" role="presentation" src=${input.imageUri}>
118
122
  </div>
119
123
  <div class="action-and-text-container">
@@ -135,12 +139,14 @@ function revealBadgeSettings(): void {
135
139
  }
136
140
 
137
141
  export class BadgeNotification extends UI.Widget.Widget {
142
+ jslogContext = '';
138
143
  message: HTMLElement|string = '';
139
144
  imageUri = '';
140
145
  actions: BadgeNotificationAction[] = [];
146
+ isStarterBadge = false;
141
147
 
148
+ #autoCloseTimeout?: number;
142
149
  #view: View;
143
-
144
150
  constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
145
151
  super(element);
146
152
  this.#view = view;
@@ -170,12 +176,19 @@ export class BadgeNotification extends UI.Widget.Widget {
170
176
  this.message = properties.message;
171
177
  this.imageUri = properties.imageUri;
172
178
  this.actions = properties.actions;
179
+ this.isStarterBadge = properties.isStarterBadge;
180
+ this.jslogContext = properties.jslogContext;
173
181
  this.requestUpdate();
174
182
  this.show(document.body);
175
183
 
176
184
  void this.updateComplete.then(() => {
177
185
  this.#positionNotification();
178
186
  });
187
+
188
+ if (this.#autoCloseTimeout) {
189
+ window.clearTimeout(this.#autoCloseTimeout);
190
+ }
191
+ this.#autoCloseTimeout = window.setTimeout(this.#onAutoClose, AUTO_CLOSE_TIME_IN_MS);
179
192
  }
180
193
 
181
194
  async #presentStarterBadge(badge: Badges.Badge): Promise<void> {
@@ -183,7 +196,7 @@ export class BadgeNotification extends UI.Widget.Widget {
183
196
  const receiveBadgesSettingEnabled = Badges.UserBadges.instance().isReceiveBadgesSettingEnabled();
184
197
  const googleDeveloperProgramLink = UI.XLink.XLink.create(
185
198
  'https://developers.google.com/program', lockedString('Google Developer Program'), 'badge-link', undefined,
186
- 'gdp.program-link');
199
+ 'program-link');
187
200
 
188
201
  // If the user already has a GDP profile and the receive badges setting enabled,
189
202
  // starter badge behaves as if it's an activity based badge.
@@ -198,20 +211,27 @@ export class BadgeNotification extends UI.Widget.Widget {
198
211
  this.#show({
199
212
  message: i18nFormatString(
200
213
  UIStrings.starterBadgeAwardMessageSettingDisabled, {PH1: badge.title, PH2: googleDeveloperProgramLink}),
214
+ jslogContext: badge.name,
201
215
  actions: [
202
216
  {
203
217
  label: i18nString(UIStrings.remindMeLater),
204
- onClick: () => {/* To implement */},
218
+ jslogContext: 'remind-me-later',
219
+ onClick: () => {
220
+ this.detach();
221
+ Badges.UserBadges.instance().snoozeStarterBadge();
222
+ },
205
223
  },
206
224
  {
207
225
  label: i18nString(UIStrings.receiveBadges),
226
+ jslogContext: 'receive-badges',
208
227
  onClick: () => {
209
- this.#close();
228
+ this.detach();
210
229
  revealBadgeSettings();
211
230
  }
212
231
  }
213
232
  ],
214
233
  imageUri: badge.imageUri,
234
+ isStarterBadge: true,
215
235
  });
216
236
  return;
217
237
  }
@@ -220,47 +240,77 @@ export class BadgeNotification extends UI.Widget.Widget {
220
240
  this.#show({
221
241
  message: i18nFormatString(
222
242
  UIStrings.starterBadgeAwardMessageNoGdpProfile, {PH1: badge.title, PH2: googleDeveloperProgramLink}),
243
+ jslogContext: badge.name,
223
244
  actions: [
224
245
  {
225
246
  label: i18nString(UIStrings.remindMeLater),
226
- onClick: () => {/* TODO(ergunsh): Implement */},
247
+ jslogContext: 'remind-me-later',
248
+ onClick: () => {
249
+ this.detach();
250
+ Badges.UserBadges.instance().snoozeStarterBadge();
251
+ },
227
252
  },
228
253
  {
229
254
  label: i18nString(UIStrings.createProfile),
255
+ jslogContext: 'create-profile',
230
256
  onClick: () => {
231
- this.#close();
232
- GdpSignUpDialog.GdpSignUpDialog.show();
257
+ this.detach();
258
+ GdpSignUpDialog.GdpSignUpDialog.show({
259
+ // We want to consider cancelling from the starter badge as a "snooze" for starter badge.
260
+ onCancel: () => Badges.UserBadges.instance().snoozeStarterBadge(),
261
+ });
233
262
  }
234
263
  }
235
264
  ],
236
265
  imageUri: badge.imageUri,
266
+ isStarterBadge: true,
237
267
  });
238
268
  }
239
269
 
240
270
  #presentActivityBasedBadge(badge: Badges.Badge): void {
241
271
  this.#show({
242
272
  message: i18nString(UIStrings.activityBasedBadgeAwardMessage, {PH1: badge.title}),
273
+ jslogContext: badge.name,
243
274
  actions: [
244
275
  {
245
276
  label: i18nString(UIStrings.manageSettings),
277
+ jslogContext: 'manage-settings',
246
278
  onClick: () => {
247
- this.#close();
279
+ this.detach();
248
280
  revealBadgeSettings();
249
281
  },
250
282
  },
251
283
  {
252
284
  label: i18nString(UIStrings.viewProfile),
285
+ jslogContext: 'view-profile',
253
286
  onClick: () => {
254
287
  UI.UIUtils.openInNewTab(Host.GdpClient.GOOGLE_DEVELOPER_PROGRAM_PROFILE_LINK);
255
288
  }
256
289
  }
257
290
  ],
258
291
  imageUri: badge.imageUri,
292
+ isStarterBadge: badge.isStarterBadge,
259
293
  });
260
294
  }
261
295
 
262
- #close = (): void => {
296
+ override onDetach(): void {
297
+ window.clearTimeout(this.#autoCloseTimeout);
298
+ }
299
+
300
+ #onDismissClick = (): void => {
301
+ this.detach();
302
+
303
+ if (this.isStarterBadge) {
304
+ Badges.UserBadges.instance().dismissStarterBadge();
305
+ }
306
+ };
307
+
308
+ #onAutoClose = (): void => {
263
309
  this.detach();
310
+
311
+ if (this.isStarterBadge) {
312
+ Badges.UserBadges.instance().snoozeStarterBadge();
313
+ }
264
314
  };
265
315
 
266
316
  override wasShown(): void {
@@ -273,7 +323,9 @@ export class BadgeNotification extends UI.Widget.Widget {
273
323
  message: this.message,
274
324
  imageUri: this.imageUri,
275
325
  actions: this.actions,
276
- onCloseClick: this.#close,
326
+ isStarterBadge: this.isStarterBadge,
327
+ onDismissClick: this.#onDismissClick,
328
+ jslogContext: this.jslogContext,
277
329
  };
278
330
  this.#view(viewInput, undefined, this.contentElement);
279
331
  }
@@ -137,7 +137,7 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
137
137
  <devtools-switch
138
138
  .checked=${input.keepMeUpdated}
139
139
  @switchchange=${(e: Switch.Switch.SwitchChangeEvent) => input.onKeepMeUpdatedChange(e.checked)}
140
- jslog=${VisualLogging.toggle('gdp.signup.keep-me-updated').track({ click: true })}
140
+ jslog=${VisualLogging.toggle('keep-me-updated').track({ click: true })}
141
141
  aria-label=${i18nString(UIStrings.keepUpdated)}
142
142
  >
143
143
  </devtools-switch>
@@ -152,11 +152,11 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
152
152
  <div class="section-text">
153
153
  <div>${i18nString(UIStrings.tailorProfileBody)}</div><br/>
154
154
  <div>${i18n.i18n.getFormatLocalizedString(str_, UIStrings.tailorProfileBodyDisclaimer, {
155
- PH1: UI.XLink.XLink.create(CONTENT_POLICY_URL, i18nString(UIStrings.contentPolicy), 'link', undefined, 'gdp.content-policy'),
155
+ PH1: UI.XLink.XLink.create(CONTENT_POLICY_URL, i18nString(UIStrings.contentPolicy), 'link', undefined, 'content-policy'),
156
156
  PH2: UI.XLink.XLink.create(TERMS_OF_SERVICE_URL, i18nString(UIStrings.termsOfService), 'link',
157
- undefined, 'gdp.terms-of-service'),
157
+ undefined, 'terms-of-service'),
158
158
  PH3: UI.XLink.XLink.create(PRIVACY_POLICY_URL, i18nString(UIStrings.privacyPolicy), 'link',
159
- undefined, 'gdp.privacy-policy'),
159
+ undefined, 'privacy-policy'),
160
160
  })}</div>
161
161
  </div>
162
162
  </div>
@@ -176,7 +176,7 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
176
176
  @click=${input.onCancelClick}>${i18nString(UIStrings.cancel)}</devtools-button>
177
177
  <devtools-button
178
178
  .variant=${Buttons.Button.Variant.PRIMARY}
179
- .jslogContext=${'gdp.sign-up'}
179
+ .jslogContext=${'sign-up'}
180
180
  .spinner=${input.isSigningUp}
181
181
  .disabled=${input.isSigningUp}
182
182
  @click=${input.onSignUpClick}>${i18nString(UIStrings.signUp)}</devtools-button>
@@ -194,11 +194,19 @@ export class GdpSignUpDialog extends UI.Widget.VBox {
194
194
  #keepMeUpdated = false;
195
195
  #isSigningUp = false;
196
196
  #onSuccess?: () => void;
197
+ #onCancel?: () => void;
197
198
 
198
- constructor(options: {dialog: UI.Dialog.Dialog, onSuccess?: () => void}, view?: View) {
199
+ constructor(
200
+ options: {
201
+ dialog: UI.Dialog.Dialog,
202
+ onSuccess?: () => void,
203
+ onCancel?: () => void,
204
+ },
205
+ view?: View) {
199
206
  super();
200
207
  this.#dialog = options.dialog;
201
208
  this.#onSuccess = options.onSuccess;
209
+ this.#onCancel = options.onCancel;
202
210
  this.#view = view ?? DEFAULT_VIEW;
203
211
  this.requestUpdate();
204
212
  }
@@ -231,6 +239,7 @@ export class GdpSignUpDialog extends UI.Widget.VBox {
231
239
  onSignUpClick: this.#onSignUpClick.bind(this),
232
240
  onCancelClick: () => {
233
241
  this.#dialog.hide();
242
+ this.#onCancel?.();
234
243
  },
235
244
  keepMeUpdated: this.#keepMeUpdated,
236
245
  onKeepMeUpdatedChange: (value: boolean) => {
@@ -243,14 +252,14 @@ export class GdpSignUpDialog extends UI.Widget.VBox {
243
252
  this.#view(viewInput, undefined, this.contentElement);
244
253
  }
245
254
 
246
- static show({onSuccess}: {onSuccess?: () => void} = {}): void {
247
- const dialog = new UI.Dialog.Dialog();
255
+ static show({onSuccess, onCancel}: {onSuccess?: () => void, onCancel?: () => void} = {}): void {
256
+ const dialog = new UI.Dialog.Dialog('gdp-sign-up-dialog');
248
257
  dialog.setAriaLabel(i18nString(UIStrings.gdpDialogAriaLabel));
249
258
  dialog.setMaxContentSize(new Geometry.Size(384, 500));
250
259
  dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.SET_EXACT_WIDTH_MAX_HEIGHT);
251
260
  dialog.setDimmed(true);
252
261
 
253
- new GdpSignUpDialog({dialog, onSuccess}).show(dialog.contentElement);
262
+ new GdpSignUpDialog({dialog, onSuccess, onCancel}).show(dialog.contentElement);
254
263
  dialog.show(undefined, /* stack */ true);
255
264
  }
256
265
  }
@@ -529,7 +529,7 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin<EventTypes, t
529
529
  this.teaser = undefined;
530
530
  }
531
531
  this.aiCodeCompletion = new AiCodeCompletion.AiCodeCompletion.AiCodeCompletion(
532
- {aidaClient: this.aidaClient}, this.editor, AiCodeCompletion.AiCodeCompletion.Panel.CONSOLE, ['\n\n']);
532
+ {aidaClient: this.aidaClient}, this.editor, AiCodeCompletion.AiCodeCompletion.ContextFlavor.CONSOLE, ['\n\n']);
533
533
  this.aiCodeCompletion.addEventListener(AiCodeCompletion.AiCodeCompletion.Events.RESPONSE_RECEIVED, event => {
534
534
  this.aiCodeCompletionCitations = event.data.citations;
535
535
  this.dispatchEventToListeners(Events.AI_CODE_COMPLETION_RESPONSE_RECEIVED, event.data);
@@ -270,6 +270,7 @@ let consoleViewInstance: ConsoleView;
270
270
 
271
271
  const MIN_HISTORY_LENGTH_FOR_DISABLING_SELF_XSS_WARNING = 5;
272
272
  const DISCLAIMER_TOOLTIP_ID = 'console-ai-code-completion-disclaimer-tooltip';
273
+ const SPINNER_TOOLTIP_ID = 'console-ai-code-completion-spinner-tooltip';
273
274
  const CITATIONS_TOOLTIP_ID = 'console-ai-code-completion-citations-tooltip';
274
275
 
275
276
  export class ConsoleView extends UI.Widget.VBox implements
@@ -624,8 +625,11 @@ export class ConsoleView extends UI.Widget.VBox implements
624
625
  }
625
626
 
626
627
  createAiCodeCompletionSummaryToolbar(): void {
627
- this.aiCodeCompletionSummaryToolbar = new AiCodeCompletionSummaryToolbar(
628
- {citationsTooltipId: CITATIONS_TOOLTIP_ID, disclaimerTooltipId: DISCLAIMER_TOOLTIP_ID});
628
+ this.aiCodeCompletionSummaryToolbar = new AiCodeCompletionSummaryToolbar({
629
+ citationsTooltipId: CITATIONS_TOOLTIP_ID,
630
+ disclaimerTooltipId: DISCLAIMER_TOOLTIP_ID,
631
+ spinnerTooltipId: SPINNER_TOOLTIP_ID
632
+ });
629
633
  this.aiCodeCompletionSummaryToolbarContainer = this.element.createChild('div');
630
634
  this.aiCodeCompletionSummaryToolbar.show(this.aiCodeCompletionSummaryToolbarContainer, undefined, true);
631
635
  }
@@ -205,7 +205,6 @@ class ColorRenderer extends rendererBase(SDK.CSSPropertyParserMatchers.ColorMatc
205
205
  swatch.renderColor(color);
206
206
  const valueElement = document.createElement('span');
207
207
  valueElement.textContent = match.text;
208
- swatch.append(valueElement);
209
208
 
210
209
  swatch.addEventListener(
211
210
  InlineEditor.ColorSwatch.ColorChangedEvent.eventName, (event: InlineEditor.ColorSwatch.ColorChangedEvent) => {
@@ -214,7 +213,7 @@ class ColorRenderer extends rendererBase(SDK.CSSPropertyParserMatchers.ColorMatc
214
213
  });
215
214
 
216
215
  context.addControl('color', swatch);
217
- return [swatch];
216
+ return [swatch, valueElement];
218
217
  }
219
218
 
220
219
  matcher(): SDK.CSSPropertyParserMatchers.ColorMatcher {
@@ -774,6 +774,10 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
774
774
  this.#domTreeWidget.selectDOMNode(node, focus);
775
775
  }
776
776
 
777
+ highlightNodeAttribute(node: SDK.DOMModel.DOMNode, attribute: string): void {
778
+ this.#domTreeWidget.highlightNodeAttribute(node, attribute);
779
+ }
780
+
777
781
  selectAndShowSidebarTab(tabId: SidebarPaneTabId): void {
778
782
  if (!this.sidebarPaneView) {
779
783
  return;
@@ -488,6 +488,24 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
488
488
  }
489
489
  }
490
490
 
491
+ highlightAttribute(attributeName: string): void {
492
+ // If the attribute is not found, we highlight the tag name instead.
493
+ let animationElement = this.listItemElement.querySelector('.webkit-html-tag-name') ?? this.listItemElement;
494
+
495
+ if (this.nodeInternal.getAttribute(attributeName) !== undefined) {
496
+ const tag = this.listItemElement.getElementsByClassName('webkit-html-tag')[0];
497
+ const attributes = tag.getElementsByClassName('webkit-html-attribute');
498
+ for (const attribute of attributes) {
499
+ const attributeElement = attribute.getElementsByClassName('webkit-html-attribute-name')[0];
500
+ if (attributeElement.textContent === attributeName) {
501
+ animationElement = attributeElement;
502
+ break;
503
+ }
504
+ }
505
+ }
506
+ UI.UIUtils.runCSSAnimationOnce(animationElement, 'dom-update-highlight');
507
+ }
508
+
491
509
  isClosingTag(): boolean {
492
510
  return !isOpeningTag(this.tagTypeContext);
493
511
  }
@@ -260,6 +260,10 @@ export class DOMTreeWidget extends UI.Widget.Widget {
260
260
  this.#viewOutput?.elementsTreeOutline?.selectDOMNode(node, focus);
261
261
  }
262
262
 
263
+ highlightNodeAttribute(node: SDK.DOMModel.DOMNode, attribute: string): void {
264
+ this.#viewOutput?.elementsTreeOutline?.highlightNodeAttribute(node, attribute);
265
+ }
266
+
263
267
  setWordWrap(wrap: boolean): void {
264
268
  this.#wrap = wrap;
265
269
  this.performUpdate();
@@ -1006,6 +1010,15 @@ export class ElementsTreeOutline extends
1006
1010
  treeElement.revealAndSelect(omitFocus);
1007
1011
  }
1008
1012
 
1013
+ highlightNodeAttribute(node: SDK.DOMModel.DOMNode, attribute: string): void {
1014
+ const treeElement = this.findTreeElement(node);
1015
+ if (!treeElement) {
1016
+ return;
1017
+ }
1018
+ treeElement.reveal();
1019
+ treeElement.highlightAttribute(attribute);
1020
+ }
1021
+
1009
1022
  treeElementFromEventInternal(event: MouseEvent): UI.TreeOutline.TreeElement|null {
1010
1023
  const scrollContainer = this.element.parentElement;
1011
1024
  if (!scrollContainer) {
@@ -284,7 +284,7 @@ const DEFAULT_VIEW: View = (input, output, target) => {
284
284
  render(html`
285
285
  <div style="min-width: min-content;" jslog=${VisualLogging.pane('layout').track({resize: true})}>
286
286
  <style>${layoutPaneStyles}</style>
287
- <style>${UI.inspectorCommonStyles}</style>
287
+ <style>@scope to (devtools-widget > *) { ${UI.inspectorCommonStyles} }</style>
288
288
  <details open>
289
289
  <summary class="header"
290
290
  @keydown=${input.onSummaryKeyDown}
@@ -52,7 +52,7 @@ import {
52
52
  StylesSidebarPane,
53
53
  } from './StylesSidebarPane.js';
54
54
 
55
- const {html, nothing, render, Directives: {classMap, ifDefined}} = Lit;
55
+ const {html, nothing, render, Directives: {classMap}} = Lit;
56
56
  const ASTUtils = SDK.CSSPropertyParser.ASTUtils;
57
57
  const FlexboxEditor = ElementsComponents.StylePropertyEditor.FlexboxEditor;
58
58
  const GridEditor = ElementsComponents.StylePropertyEditor.GridEditor;
@@ -446,19 +446,25 @@ export class AttributeRenderer extends rendererBase(SDK.CSSPropertyParserMatcher
446
446
  const attrCall =
447
447
  this.#treeElement?.getTracingTooltip('attr', match.node, this.#matchedStyles, this.#computedStyles, context);
448
448
  const tooltipId = attributeMissing ? undefined : this.#treeElement?.getTooltipId('custom-attribute');
449
+ const tooltip = tooltipId ? {tooltipId} : undefined;
449
450
  // clang-format off
450
451
  render(html`
451
452
  <span data-title=${computedValue || ''}
452
453
  jslog=${VisualLogging.link('css-variable').track({click: true, hover: true})}
453
- >${attrCall ?? 'attr'}(<span class=${attributeClass} aria-details=${ifDefined(tooltipId)}>${match.name}</span>${
454
- match.type ? html` <span class=${typeClass}>${match.type}</span>` : nothing
455
- }${renderedFallback ? html`, <span class=${fallbackClass}>${renderedFallback.nodes}</span>` : nothing
456
- })</span>${tooltipId ? html`
454
+ >${attrCall ?? 'attr'}(<devtools-link-swatch class=${attributeClass} .data=${{
455
+ tooltip,
456
+ text: match.name,
457
+ isDefined: true,
458
+ onLinkActivate: () => this.#handleAttributeActivate(this.#matchedStyles.originatingNodeForStyle(match.style), match.name),
459
+ }}></devtools-link-swatch>${tooltipId ? html`
457
460
  <devtools-tooltip
458
461
  id=${tooltipId}
459
462
  variant=rich
460
463
  jslogContext=elements.css-var
461
- >${JSON.stringify(rawValue)}</devtools-tooltip>` : ''}`, varSwatch);
464
+ >${JSON.stringify(rawValue)}</devtools-tooltip>` : nothing}${
465
+ match.type ? html` <span class=${typeClass}>${match.type}</span>` : nothing
466
+ }${renderedFallback ? html`, <span class=${fallbackClass}>${renderedFallback.nodes}</span>` : nothing
467
+ })</span>`, varSwatch);
462
468
  // clang-format on
463
469
 
464
470
  const color = computedValue && Common.Color.parse(computedValue);
@@ -478,6 +484,15 @@ export class AttributeRenderer extends rendererBase(SDK.CSSPropertyParserMatcher
478
484
 
479
485
  return [colorSwatch, varSwatch];
480
486
  }
487
+
488
+ #handleAttributeActivate(node: SDK.DOMModel.DOMNode|null, attribute: string): void {
489
+ if (!node) {
490
+ return;
491
+ }
492
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AttributeLinkClicked);
493
+ Host.userMetrics.swatchActivated(Host.UserMetrics.SwatchType.ATTR_LINK);
494
+ ElementsPanel.instance().highlightNodeAttribute(node, attribute);
495
+ }
481
496
  }
482
497
 
483
498
  // clang-format off
@@ -19,7 +19,7 @@ function getGroupDefaultTextColor(): string {
19
19
  const DefaultStyle: () => PerfUI.FlameChart.GroupStyle = () => ({
20
20
  height: 20,
21
21
  padding: 2,
22
- collapsible: false,
22
+ collapsible: PerfUI.FlameChart.GroupCollapsibleState.NEVER,
23
23
  font: defaultFont,
24
24
  color: getGroupDefaultTextColor(),
25
25
  backgroundColor: 'rgba(100 0 0 / 10%)',
@@ -2391,6 +2391,10 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2391
2391
  Lastly we replace new lines with ^ and TWO new lines because the first
2392
2392
  new line is there to enact the escape command the second is the character
2393
2393
  to escape (in this case new line).
2394
+
2395
+ All other whitespace characters are replaced with a single space, as there
2396
+ is no way to enter their literal values in a command line, and they do break
2397
+ the command allowing for injection.
2394
2398
  */
2395
2399
  const encapsChars = '^"';
2396
2400
  return encapsChars +
@@ -2398,7 +2402,7 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2398
2402
  .replace(/"/g, '\\"')
2399
2403
  .replace(/[^a-zA-Z0-9\s_\-:=+~'\/.',?;()*`]/g, '^$&')
2400
2404
  .replace(/%(?=[a-zA-Z0-9_])/g, '%^')
2401
- .replace(/[^\S \r\n]/g, '^$&')
2405
+ .replace(/[^\S \r\n]/g, ' ')
2402
2406
  .replace(/\r?\n|\r/g, '^\n\n') +
2403
2407
  encapsChars;
2404
2408
  }