chrome-devtools-frontend 1.0.1516909 → 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 (94) 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 +4 -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 +166 -49
  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 +17 -11
  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 +21 -3
  34. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
  35. package/front_end/models/trace/README.md +28 -1
  36. package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
  37. package/front_end/models/trace/helpers/Trace.ts +99 -43
  38. package/front_end/models/trace/types/TraceEvents.ts +9 -0
  39. package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
  40. package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
  41. package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
  42. package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
  43. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +16 -2
  44. package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
  45. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +42 -0
  46. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
  47. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
  48. package/front_end/panels/common/BadgeNotification.ts +21 -5
  49. package/front_end/panels/common/GdpSignUpDialog.ts +18 -9
  50. package/front_end/panels/console/ConsolePrompt.ts +1 -1
  51. package/front_end/panels/console/ConsoleView.ts +6 -2
  52. package/front_end/panels/elements/ElementsPanel.ts +4 -0
  53. package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
  54. package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
  55. package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
  56. package/front_end/panels/media/TickingFlameChart.ts +1 -1
  57. package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
  58. package/front_end/panels/search/SearchResultsPane.ts +124 -128
  59. package/front_end/panels/search/SearchView.ts +24 -17
  60. package/front_end/panels/settings/components/SyncSection.ts +16 -8
  61. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -1
  62. package/front_end/panels/sources/SourcesPanel.ts +3 -0
  63. package/front_end/panels/timeline/AppenderUtils.ts +2 -2
  64. package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
  65. package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
  66. package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
  67. package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
  68. package/front_end/panels/timeline/ThreadAppender.ts +12 -3
  69. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
  70. package/front_end/panels/timeline/TimelinePanel.ts +3 -2
  71. package/front_end/panels/timeline/TimelineUIUtils.ts +5 -4
  72. package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
  73. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
  74. package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
  75. package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
  76. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
  77. package/front_end/third_party/chromium/README.chromium +1 -1
  78. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  79. package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
  80. package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
  81. package/front_end/third_party/codemirror.next/package.json +2 -1
  82. package/front_end/third_party/diff/README.chromium +1 -0
  83. package/front_end/ui/components/text_editor/config.ts +6 -7
  84. package/front_end/ui/components/tooltips/Tooltip.ts +70 -31
  85. package/front_end/ui/legacy/README.md +33 -24
  86. package/front_end/ui/legacy/SearchableView.ts +19 -26
  87. package/front_end/ui/legacy/TextPrompt.ts +166 -1
  88. package/front_end/ui/legacy/Treeoutline.ts +16 -2
  89. package/front_end/ui/legacy/UIUtils.ts +15 -2
  90. package/front_end/ui/legacy/XElement.ts +0 -43
  91. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
  92. package/front_end/ui/visual_logging/KnownContextValues.ts +19 -6
  93. package/front_end/ui/visual_logging/README.md +43 -27
  94. package/package.json +1 -1
@@ -4,6 +4,7 @@
4
4
  * found in the LICENSE file.
5
5
  */
6
6
 
7
+ @scope to (devtools-widget > *) {
7
8
  .ax-name {
8
9
  color: var(--sys-color-token-attribute);
9
10
  flex-shrink: 0;
@@ -38,3 +39,4 @@ span.ax-internal-role {
38
39
  overflow-x: hidden;
39
40
  white-space: normal;
40
41
  }
42
+ }
@@ -35,6 +35,8 @@ import {
35
35
  type Step
36
36
  } from './components/ChatView.js';
37
37
  import {ExploreWidget} from './components/ExploreWidget.js';
38
+ import {MarkdownRendererWithCodeBlock} from './components/MarkdownRendererWithCodeBlock.js';
39
+ import {PerformanceAgentMarkdownRenderer} from './components/PerformanceAgentMarkdownRenderer.js';
38
40
  import {isAiAssistancePatchingEnabled} from './PatchWidget.js';
39
41
 
40
42
  const {html} = Lit;
@@ -263,6 +265,16 @@ async function getEmptyStateSuggestions(
263
265
  }
264
266
  }
265
267
 
268
+ function getMarkdownRenderer(context: AiAssistanceModel.ConversationContext<unknown>|null):
269
+ MarkdownRendererWithCodeBlock {
270
+ if (context instanceof AiAssistanceModel.PerformanceTraceContext && !context.external) {
271
+ const focus = context.getItem();
272
+ return new PerformanceAgentMarkdownRenderer(focus.lookupEvent.bind(focus));
273
+ }
274
+
275
+ return new MarkdownRendererWithCodeBlock();
276
+ }
277
+
266
278
  interface ToolbarViewInput {
267
279
  onNewChatClick: () => void;
268
280
  populateHistoryMenu: (contextMenu: UI.ContextMenu.ContextMenu) => void;
@@ -837,6 +849,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
837
849
 
838
850
  override async performUpdate(): Promise<void> {
839
851
  const emptyStateSuggestions = await getEmptyStateSuggestions(this.#selectedContext, this.#conversation);
852
+ const markdownRenderer = getMarkdownRenderer(this.#selectedContext);
840
853
 
841
854
  this.view(
842
855
  {
@@ -865,6 +878,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
865
878
  changeManager: this.#changeManager,
866
879
  uploadImageInputEnabled: isAiAssistanceMultimodalUploadInputEnabled() &&
867
880
  this.#conversation?.type === AiAssistanceModel.ConversationType.STYLING,
881
+ markdownRenderer,
868
882
  onNewChatClick: this.#handleNewChatRequest.bind(this),
869
883
  populateHistoryMenu: this.#populateHistoryMenu.bind(this),
870
884
  onDeleteClick: this.#onDeleteClicked.bind(this),
@@ -1054,8 +1068,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1054
1068
  const focus = context.getItem().data;
1055
1069
  if (focus.callTree) {
1056
1070
  const event = focus.callTree.selectedNode?.event ?? focus.callTree.rootNode.event;
1057
- const trace = new SDK.TraceObject.RevealableEvent(event);
1058
- return Common.Revealer.reveal(trace);
1071
+ const revealable = new SDK.TraceObject.RevealableEvent(event);
1072
+ return Common.Revealer.reveal(revealable);
1059
1073
  }
1060
1074
  if (focus.insight) {
1061
1075
  return Common.Revealer.reveal(focus.insight);
@@ -18,13 +18,13 @@ import * as PanelUtils from '../../../panels/utils/utils.js';
18
18
  import * as Marked from '../../../third_party/marked/marked.js';
19
19
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
20
20
  import type * as MarkdownView from '../../../ui/components/markdown_view/markdown_view.js';
21
+ import type {MarkdownLitRenderer} from '../../../ui/components/markdown_view/MarkdownView.js';
21
22
  import * as UI from '../../../ui/legacy/legacy.js';
22
23
  import * as Lit from '../../../ui/lit/lit.js';
23
24
  import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
24
25
  import {PatchWidget} from '../PatchWidget.js';
25
26
 
26
27
  import chatViewStyles from './chatView.css.js';
27
- import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js';
28
28
  import {UserActionRow} from './UserActionRow.js';
29
29
 
30
30
  const {html, Directives: {ifDefined, ref}} = Lit;
@@ -303,11 +303,11 @@ export interface Props {
303
303
  disclaimerText: Platform.UIString.LocalizedString;
304
304
  isTextInputEmpty: boolean;
305
305
  uploadImageInputEnabled?: boolean;
306
+ markdownRenderer: MarkdownLitRenderer;
306
307
  }
307
308
 
308
309
  export class ChatView extends HTMLElement {
309
310
  readonly #shadow = this.attachShadow({mode: 'open'});
310
- #markdownRenderer = new MarkdownRendererWithCodeBlock();
311
311
  #scrollTop?: number;
312
312
  #props: Props;
313
313
  #messagesContainerElement?: Element;
@@ -338,7 +338,6 @@ export class ChatView extends HTMLElement {
338
338
  }
339
339
 
340
340
  set props(props: Props) {
341
- this.#markdownRenderer = new MarkdownRendererWithCodeBlock();
342
341
  this.#props = props;
343
342
  this.#render();
344
343
  }
@@ -624,7 +623,7 @@ export class ChatView extends HTMLElement {
624
623
  isTextInputDisabled: this.#props.isTextInputDisabled,
625
624
  suggestions: this.#props.emptyStateSuggestions,
626
625
  userInfo: this.#props.userInfo,
627
- markdownRenderer: this.#markdownRenderer,
626
+ markdownRenderer: this.#props.markdownRenderer,
628
627
  conversationType: this.#props.conversationType,
629
628
  changeSummary: this.#props.changeSummary,
630
629
  changeManager: this.#props.changeManager,
@@ -672,7 +671,7 @@ export class ChatView extends HTMLElement {
672
671
  }
673
672
  }
674
673
 
675
- function renderTextAsMarkdown(text: string, markdownRenderer: MarkdownRendererWithCodeBlock, {animate, ref: refFn}: {
674
+ function renderTextAsMarkdown(text: string, markdownRenderer: MarkdownLitRenderer, {animate, ref: refFn}: {
676
675
  animate?: boolean,
677
676
  ref?: (element?: Element) => void,
678
677
  } = {}): Lit.TemplateResult {
@@ -753,7 +752,7 @@ function renderStepDetails({
753
752
  isLast,
754
753
  }: {
755
754
  step: Step,
756
- markdownRenderer: MarkdownRendererWithCodeBlock,
755
+ markdownRenderer: MarkdownLitRenderer,
757
756
  isLast: boolean,
758
757
  }): Lit.LitTemplate {
759
758
  const sideEffects = isLast && step.sideEffect ? renderSideEffectConfirmationUi(step) : Lit.nothing;
@@ -817,7 +816,7 @@ function renderStepBadge({step, isLoading, isLast}: {
817
816
  function renderStep({step, isLoading, markdownRenderer, isLast}: {
818
817
  step: Step,
819
818
  isLoading: boolean,
820
- markdownRenderer: MarkdownRendererWithCodeBlock,
819
+ markdownRenderer: MarkdownLitRenderer,
821
820
  isLast: boolean,
822
821
  }): Lit.LitTemplate {
823
822
  const stepClasses = Lit.Directives.classMap({
@@ -926,7 +925,7 @@ function renderChatMessage({
926
925
  canShowFeedbackForm: boolean,
927
926
  isLast: boolean,
928
927
  userInfo: Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'>,
929
- markdownRenderer: MarkdownRendererWithCodeBlock,
928
+ markdownRenderer: MarkdownLitRenderer,
930
929
  onSuggestionClick: (suggestion: string) => void,
931
930
  onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void,
932
931
  onCopyResponseClick: (message: ModelChatMessage) => void,
@@ -1156,7 +1155,7 @@ function renderMessages({
1156
1155
  isReadOnly: boolean,
1157
1156
  canShowFeedbackForm: boolean,
1158
1157
  userInfo: Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'>,
1159
- markdownRenderer: MarkdownRendererWithCodeBlock,
1158
+ markdownRenderer: MarkdownLitRenderer,
1160
1159
  onSuggestionClick: (suggestion: string) => void,
1161
1160
  onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void,
1162
1161
  onCopyResponseClick: (message: ModelChatMessage) => void,
@@ -1657,7 +1656,7 @@ function renderMainContents({
1657
1656
  isTextInputDisabled: boolean,
1658
1657
  suggestions: AiAssistanceModel.ConversationSuggestion[],
1659
1658
  userInfo: Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'>,
1660
- markdownRenderer: MarkdownRendererWithCodeBlock,
1659
+ markdownRenderer: MarkdownLitRenderer,
1661
1660
  changeManager: AiAssistanceModel.ChangeManager,
1662
1661
  onSuggestionClick: (suggestion: string) => void,
1663
1662
  onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void,
@@ -0,0 +1,42 @@
1
+ // Copyright 2025 The Chromium Authors
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 Trace from '../../../models/trace/trace.js';
8
+ import type * as Marked from '../../../third_party/marked/marked.js';
9
+ import * as Lit from '../../../ui/lit/lit.js';
10
+
11
+ import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js';
12
+
13
+ const {html} = Lit;
14
+
15
+ export class PerformanceAgentMarkdownRenderer extends MarkdownRendererWithCodeBlock {
16
+ constructor(private lookupEvent: (key: Trace.Types.File.SerializableKey) => Trace.Types.Events.Event | null) {
17
+ super();
18
+ }
19
+
20
+ override templateForToken(token: Marked.Marked.MarkedToken): Lit.TemplateResult|null {
21
+ if (token.type === 'link' && token.href.startsWith('#')) {
22
+ const event = this.lookupEvent(token.href.slice(1) as Trace.Types.File.SerializableKey);
23
+ if (event) {
24
+ let label = token.text;
25
+ let title = '';
26
+ if (Trace.Types.Events.isSyntheticNetworkRequest(event)) {
27
+ title = event.args.data.url;
28
+ } else {
29
+ label += ` (${event.name})`;
30
+ }
31
+
32
+ // eslint-disable-next-line rulesdir/no-a-tags-in-lit
33
+ return html`<a href="#" draggable=false .title=${title} @click=${(e: Event) => {
34
+ e.stopPropagation();
35
+ void Common.Revealer.reveal(new SDK.TraceObject.RevealableEvent(event));
36
+ }}>${label}</a>`;
37
+ }
38
+ }
39
+
40
+ return super.templateForToken(token);
41
+ }
42
+ }
@@ -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,
@@ -73,13 +73,14 @@ const AUTO_CLOSE_TIME_IN_MS = 30000;
73
73
 
74
74
  export interface BadgeNotificationAction {
75
75
  label: string;
76
- jslogContext?: string;
76
+ jslogContext: string;
77
77
  title?: string;
78
78
  onClick: () => void;
79
79
  }
80
80
 
81
81
  export interface BadgeNotificationProperties {
82
82
  message: HTMLElement|string;
83
+ jslogContext: string;
83
84
  imageUri: string;
84
85
  actions: BadgeNotificationAction[];
85
86
  isStarterBadge: boolean;
@@ -115,8 +116,8 @@ const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement)
115
116
 
116
117
  render(html`
117
118
  <style>${badgeNotificationStyles}</style>
118
- <div class="container">
119
- <div class="badge-container">
119
+ <div class="container" jslog=${VisualLogging.dialog('badge-notification')}>
120
+ <div class="badge-container" jslog=${VisualLogging.item(input.jslogContext)}>
120
121
  <img class="badge-image" role="presentation" src=${input.imageUri}>
121
122
  </div>
122
123
  <div class="action-and-text-container">
@@ -138,6 +139,7 @@ function revealBadgeSettings(): void {
138
139
  }
139
140
 
140
141
  export class BadgeNotification extends UI.Widget.Widget {
142
+ jslogContext = '';
141
143
  message: HTMLElement|string = '';
142
144
  imageUri = '';
143
145
  actions: BadgeNotificationAction[] = [];
@@ -175,6 +177,7 @@ export class BadgeNotification extends UI.Widget.Widget {
175
177
  this.imageUri = properties.imageUri;
176
178
  this.actions = properties.actions;
177
179
  this.isStarterBadge = properties.isStarterBadge;
180
+ this.jslogContext = properties.jslogContext;
178
181
  this.requestUpdate();
179
182
  this.show(document.body);
180
183
 
@@ -193,7 +196,7 @@ export class BadgeNotification extends UI.Widget.Widget {
193
196
  const receiveBadgesSettingEnabled = Badges.UserBadges.instance().isReceiveBadgesSettingEnabled();
194
197
  const googleDeveloperProgramLink = UI.XLink.XLink.create(
195
198
  'https://developers.google.com/program', lockedString('Google Developer Program'), 'badge-link', undefined,
196
- 'gdp.program-link');
199
+ 'program-link');
197
200
 
198
201
  // If the user already has a GDP profile and the receive badges setting enabled,
199
202
  // starter badge behaves as if it's an activity based badge.
@@ -208,9 +211,11 @@ export class BadgeNotification extends UI.Widget.Widget {
208
211
  this.#show({
209
212
  message: i18nFormatString(
210
213
  UIStrings.starterBadgeAwardMessageSettingDisabled, {PH1: badge.title, PH2: googleDeveloperProgramLink}),
214
+ jslogContext: badge.name,
211
215
  actions: [
212
216
  {
213
217
  label: i18nString(UIStrings.remindMeLater),
218
+ jslogContext: 'remind-me-later',
214
219
  onClick: () => {
215
220
  this.detach();
216
221
  Badges.UserBadges.instance().snoozeStarterBadge();
@@ -218,6 +223,7 @@ export class BadgeNotification extends UI.Widget.Widget {
218
223
  },
219
224
  {
220
225
  label: i18nString(UIStrings.receiveBadges),
226
+ jslogContext: 'receive-badges',
221
227
  onClick: () => {
222
228
  this.detach();
223
229
  revealBadgeSettings();
@@ -234,9 +240,11 @@ export class BadgeNotification extends UI.Widget.Widget {
234
240
  this.#show({
235
241
  message: i18nFormatString(
236
242
  UIStrings.starterBadgeAwardMessageNoGdpProfile, {PH1: badge.title, PH2: googleDeveloperProgramLink}),
243
+ jslogContext: badge.name,
237
244
  actions: [
238
245
  {
239
246
  label: i18nString(UIStrings.remindMeLater),
247
+ jslogContext: 'remind-me-later',
240
248
  onClick: () => {
241
249
  this.detach();
242
250
  Badges.UserBadges.instance().snoozeStarterBadge();
@@ -244,9 +252,13 @@ export class BadgeNotification extends UI.Widget.Widget {
244
252
  },
245
253
  {
246
254
  label: i18nString(UIStrings.createProfile),
255
+ jslogContext: 'create-profile',
247
256
  onClick: () => {
248
257
  this.detach();
249
- GdpSignUpDialog.GdpSignUpDialog.show();
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
+ });
250
262
  }
251
263
  }
252
264
  ],
@@ -258,9 +270,11 @@ export class BadgeNotification extends UI.Widget.Widget {
258
270
  #presentActivityBasedBadge(badge: Badges.Badge): void {
259
271
  this.#show({
260
272
  message: i18nString(UIStrings.activityBasedBadgeAwardMessage, {PH1: badge.title}),
273
+ jslogContext: badge.name,
261
274
  actions: [
262
275
  {
263
276
  label: i18nString(UIStrings.manageSettings),
277
+ jslogContext: 'manage-settings',
264
278
  onClick: () => {
265
279
  this.detach();
266
280
  revealBadgeSettings();
@@ -268,6 +282,7 @@ export class BadgeNotification extends UI.Widget.Widget {
268
282
  },
269
283
  {
270
284
  label: i18nString(UIStrings.viewProfile),
285
+ jslogContext: 'view-profile',
271
286
  onClick: () => {
272
287
  UI.UIUtils.openInNewTab(Host.GdpClient.GOOGLE_DEVELOPER_PROGRAM_PROFILE_LINK);
273
288
  }
@@ -310,6 +325,7 @@ export class BadgeNotification extends UI.Widget.Widget {
310
325
  actions: this.actions,
311
326
  isStarterBadge: this.isStarterBadge,
312
327
  onDismissClick: this.#onDismissClick,
328
+ jslogContext: this.jslogContext,
313
329
  };
314
330
  this.#view(viewInput, undefined, this.contentElement);
315
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
  }
@@ -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) {