chrome-devtools-frontend 1.0.1510848 → 1.0.1512349

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 (45) hide show
  1. package/front_end/Images/src/ai-explorer-badge.svg +114 -0
  2. package/front_end/Images/src/code-whisperer-badge.svg +166 -0
  3. package/front_end/Images/src/devtools-user-badge.svg +129 -0
  4. package/front_end/Images/src/dom-detective-badge.svg +136 -0
  5. package/front_end/Images/src/speedster-badge.svg +166 -0
  6. package/front_end/core/host/AidaClient.ts +2 -0
  7. package/front_end/core/host/GdpClient.ts +38 -2
  8. package/front_end/core/i18n/NumberFormatter.ts +7 -0
  9. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +3 -19
  10. package/front_end/models/ai_assistance/ai_assistance.ts +1 -1
  11. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +7 -6
  12. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +119 -119
  13. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +43 -52
  14. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +100 -100
  15. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +12 -18
  16. package/front_end/models/ai_assistance/data_formatters/UnitFormatters.ts +151 -0
  17. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +3 -0
  18. package/front_end/models/badges/Badge.ts +7 -4
  19. package/front_end/models/badges/DOMDetectiveBadge.ts +20 -0
  20. package/front_end/models/badges/SpeedsterBadge.ts +4 -1
  21. package/front_end/models/badges/StarterBadge.ts +5 -1
  22. package/front_end/models/badges/UserBadges.ts +33 -7
  23. package/front_end/models/trace/ModelImpl.ts +0 -13
  24. package/front_end/models/trace/insights/Common.ts +19 -0
  25. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +36 -9
  26. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +32 -0
  27. package/front_end/panels/common/AiCodeCompletionTeaser.ts +14 -2
  28. package/front_end/panels/common/BadgeNotification.ts +119 -9
  29. package/front_end/panels/common/badgeNotification.css +4 -0
  30. package/front_end/panels/console/ConsolePrompt.ts +26 -0
  31. package/front_end/panels/elements/ElementsTreeElement.ts +12 -0
  32. package/front_end/panels/elements/ElementsTreeOutline.ts +3 -0
  33. package/front_end/panels/elements/StylePropertiesSection.ts +3 -0
  34. package/front_end/panels/elements/StylePropertyTreeElement.ts +5 -0
  35. package/front_end/panels/settings/SettingsScreen.ts +3 -9
  36. package/front_end/panels/settings/components/SyncSection.ts +6 -2
  37. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +35 -6
  38. package/front_end/panels/timeline/TimelinePanel.ts +22 -10
  39. package/front_end/panels/timeline/TimelineUIUtils.ts +4 -3
  40. package/front_end/panels/timeline/utils/InsightAIContext.ts +0 -19
  41. package/front_end/ui/legacy/filter.css +1 -1
  42. package/front_end/ui/legacy/inspectorCommon.css +1 -1
  43. package/front_end/ui/legacy/softDropDownButton.css +1 -1
  44. package/package.json +1 -1
  45. package/front_end/models/ai_assistance/data_formatters/Types.ts +0 -9
@@ -2,7 +2,9 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import * as Host from '../../core/host/host.js';
5
6
  import * as i18n from '../../core/i18n/i18n.js';
7
+ import * as Badges from '../../models/badges/badges.js';
6
8
  import * as Buttons from '../../ui/components/buttons/buttons.js';
7
9
  import * as UI from '../../ui/legacy/legacy.js';
8
10
  import * as Lit from '../../ui/lit/lit.js';
@@ -17,10 +19,50 @@ const UIStrings = {
17
19
  * @description Title for close button
18
20
  */
19
21
  dismiss: 'Dismiss',
22
+ /**
23
+ * @description Activity based badge award notification text
24
+ * @example {Badge Title} PH1
25
+ */
26
+ activityBasedBadgeAwardMessage: 'You earned the {PH1} badge! It has been added to your Developer Profile.',
27
+ /**
28
+ * @description Action title for navigating to the badge settings in Google Developer Profile section
29
+ */
30
+ badgeSettings: 'Badge settings',
31
+ /**
32
+ * @description Action title for opening the Google Developer Program profile page of the user in a new tab
33
+ */
34
+ viewProfile: 'View profile',
35
+ /**
36
+ * @description Starter badge award notification text when the user has a Google Developer Program profile but did not enable receiving badges in DevTools yet
37
+ * @example {Badge Title} PH1
38
+ * @example {Google Developer Program link} PH2
39
+ */
40
+ starterBadgeAwardMessageSettingDisabled: 'You earned the {PH1} badge for the {PH2}! Turn on badges to claim it.',
41
+ /**
42
+ * @description Starter badge award notification text when the user does not have a Google Developer Program profile.
43
+ * @example {Badge Title} PH1
44
+ * @example {Google Developer Program link} PH2
45
+ */
46
+ starterBadgeAwardMessageNoGdpProfile:
47
+ 'You earned the {PH1} badge for the {PH2}! Create a profile to claim your badge.',
48
+ /**
49
+ * @description Action title for snoozing the starter badge.
50
+ */
51
+ remindMeLater: 'Remind me later',
52
+ /**
53
+ * @description Action title for enabling the "Receive badges" setting
54
+ */
55
+ receiveBadges: 'Receive badges',
56
+ /**
57
+ * @description Action title for creating a Google Developer Program profle
58
+ */
59
+ createProfile: 'Create profile',
20
60
  } as const;
21
61
 
22
62
  const str_ = i18n.i18n.registerUIStrings('panels/common/BadgeNotification.ts', UIStrings);
23
63
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
64
+ const i18nFormatString = i18n.i18n.getFormatLocalizedString.bind(undefined, str_);
65
+ const lockedString = i18n.i18n.lockedString;
24
66
 
25
67
  export interface BadgeNotificationAction {
26
68
  label: string;
@@ -30,7 +72,7 @@ export interface BadgeNotificationAction {
30
72
  }
31
73
 
32
74
  export interface BadgeNotificationProperties {
33
- message: Lit.LitTemplate;
75
+ message: HTMLElement|string;
34
76
  imageUri: string;
35
77
  actions: BadgeNotificationAction[];
36
78
  }
@@ -80,7 +122,7 @@ const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement)
80
122
  type View = typeof DEFAULT_VIEW;
81
123
 
82
124
  export class BadgeNotification extends UI.Widget.Widget {
83
- message: Lit.LitTemplate = html``;
125
+ message: HTMLElement|string = '';
84
126
  imageUri = '';
85
127
  actions: BadgeNotificationAction[] = [];
86
128
 
@@ -91,13 +133,81 @@ export class BadgeNotification extends UI.Widget.Widget {
91
133
  this.#view = view;
92
134
  }
93
135
 
94
- static show(properties: BadgeNotificationProperties): BadgeNotification {
95
- const widget = new BadgeNotification();
96
- widget.message = properties.message;
97
- widget.imageUri = properties.imageUri;
98
- widget.actions = properties.actions;
99
- widget.show(UI.InspectorView.InspectorView.instance().element);
100
- return widget;
136
+ async present(badge: Badges.Badge): Promise<void> {
137
+ if (badge.isStarterBadge) {
138
+ await this.#presentStarterBadge(badge);
139
+ } else {
140
+ this.#presentActivityBasedBadge(badge);
141
+ }
142
+ }
143
+
144
+ #show(properties: BadgeNotificationProperties): void {
145
+ this.message = properties.message;
146
+ this.imageUri = properties.imageUri;
147
+ this.actions = properties.actions;
148
+ this.requestUpdate();
149
+ this.show(UI.InspectorView.InspectorView.instance().element);
150
+ }
151
+
152
+ async #presentStarterBadge(badge: Badges.Badge): Promise<void> {
153
+ const gdpProfile = await Host.GdpClient.GdpClient.instance().getProfile();
154
+ const receiveBadgesSettingEnabled = Badges.UserBadges.instance().isReceiveBadgesSettingEnabled();
155
+ const googleDeveloperProgramLink = UI.XLink.XLink.create(
156
+ 'https://developers.google.com/program', lockedString('Google Developer Program'), 'badge-link', undefined,
157
+ 'gdp.program-link');
158
+
159
+ // If the user already has a GDP profile and the receive badges setting enabled,
160
+ // starter badge behaves as if it's an activity based badge.
161
+ if (gdpProfile && receiveBadgesSettingEnabled) {
162
+ this.#presentActivityBasedBadge(badge);
163
+ return;
164
+ }
165
+
166
+ // If the user already has a GDP profile and the receive badges setting disabled,
167
+ // starter badge behaves as a nudge for opting into receiving badges.
168
+ if (gdpProfile && !receiveBadgesSettingEnabled) {
169
+ this.#show({
170
+ message: i18nFormatString(
171
+ UIStrings.starterBadgeAwardMessageSettingDisabled, {PH1: badge.title, PH2: googleDeveloperProgramLink}),
172
+ actions: [
173
+ {
174
+ label: i18nString(UIStrings.remindMeLater),
175
+ onClick: () => {/* To implement */},
176
+ },
177
+ {label: i18nString(UIStrings.receiveBadges), onClick: () => { /* To implement */ }}
178
+ ],
179
+ imageUri: badge.imageUri,
180
+ });
181
+ return;
182
+ }
183
+
184
+ // The user does not have a GDP profile, starter badge acts as a nudge for creating a GDP profile.
185
+ this.#show({
186
+ message: i18nFormatString(
187
+ UIStrings.starterBadgeAwardMessageNoGdpProfile, {PH1: badge.title, PH2: googleDeveloperProgramLink}),
188
+ actions: [
189
+ {
190
+ label: i18nString(UIStrings.remindMeLater),
191
+ onClick: () => {/* TODO(ergunsh): Implement */},
192
+ },
193
+ {label: i18nString(UIStrings.createProfile), onClick: () => { /* TODO(ergunsh): Implement */ }}
194
+ ],
195
+ imageUri: badge.imageUri,
196
+ });
197
+ }
198
+
199
+ #presentActivityBasedBadge(badge: Badges.Badge): void {
200
+ this.#show({
201
+ message: i18nString(UIStrings.activityBasedBadgeAwardMessage, {PH1: badge.title}),
202
+ actions: [
203
+ {
204
+ label: i18nString(UIStrings.badgeSettings),
205
+ onClick: () => {/* TODO(ergunsh): Implement */},
206
+ },
207
+ {label: i18nString(UIStrings.viewProfile), onClick: () => { /* TODO(ergunsh): Implement */ }}
208
+ ],
209
+ imageUri: badge.imageUri,
210
+ });
101
211
  }
102
212
 
103
213
  #close = (): void => {
@@ -49,6 +49,10 @@
49
49
  background: var(--sys-color-surface);
50
50
  }
51
51
 
52
+ .badge-link {
53
+ color: var(--sys-color-inverse-primary);
54
+ }
55
+
52
56
  .message {
53
57
  width: 100%;
54
58
  color: var(--sys-color-inverse-on-surface);
@@ -71,6 +71,8 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin<EventTypes, t
71
71
  #javaScriptCompletionCompartment: CodeMirror.Compartment = new CodeMirror.Compartment();
72
72
 
73
73
  private aidaClient?: Host.AidaClient.AidaClient;
74
+ private aidaAvailability?: Host.AidaClient.AidaAccessPreconditions;
75
+ private boundOnAidaAvailabilityChange?: () => Promise<void>;
74
76
  private aiCodeCompletion?: AiCodeCompletion.AiCodeCompletion.AiCodeCompletion;
75
77
  private teaser?: PanelCommon.AiCodeCompletionTeaser;
76
78
  private placeholderCompartment: CodeMirror.Compartment = new CodeMirror.Compartment();
@@ -199,6 +201,10 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin<EventTypes, t
199
201
  if (this.isAiCodeCompletionEnabled()) {
200
202
  this.aiCodeCompletionSetting.addChangeListener(this.onAiCodeCompletionSettingChanged.bind(this));
201
203
  this.onAiCodeCompletionSettingChanged();
204
+ this.boundOnAidaAvailabilityChange = this.onAidaAvailabilityChange.bind(this);
205
+ Host.AidaClient.HostConfigTracker.instance().addEventListener(
206
+ Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED, this.boundOnAidaAvailabilityChange);
207
+ void this.onAidaAvailabilityChange();
202
208
  }
203
209
  }
204
210
 
@@ -294,6 +300,10 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin<EventTypes, t
294
300
  this.highlightingNode = false;
295
301
  SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight();
296
302
  }
303
+ if (this.boundOnAidaAvailabilityChange) {
304
+ Host.AidaClient.HostConfigTracker.instance().removeEventListener(
305
+ Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED, this.boundOnAidaAvailabilityChange);
306
+ }
297
307
  }
298
308
 
299
309
  history(): TextEditor.AutocompleteHistory.AutocompleteHistory {
@@ -505,6 +515,9 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin<EventTypes, t
505
515
  // TODO(b/435654172): Refactor and move aiCodeCompletion model one level up to avoid
506
516
  // defining additional listeners and events.
507
517
  private setAiCodeCompletion(): void {
518
+ if (this.aiCodeCompletion) {
519
+ return;
520
+ }
508
521
  if (!this.aidaClient) {
509
522
  this.aidaClient = new Host.AidaClient.AidaClient();
510
523
  }
@@ -532,6 +545,19 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin<EventTypes, t
532
545
  }
533
546
  }
534
547
 
548
+ private async onAidaAvailabilityChange(): Promise<void> {
549
+ const currentAidaAvailability = await Host.AidaClient.AidaClient.checkAccessPreconditions();
550
+ if (currentAidaAvailability !== this.aidaAvailability) {
551
+ this.aidaAvailability = currentAidaAvailability;
552
+ if (this.aidaAvailability === Host.AidaClient.AidaAccessPreconditions.AVAILABLE) {
553
+ this.onAiCodeCompletionSettingChanged();
554
+ } else if (this.aiCodeCompletion) {
555
+ this.aiCodeCompletion.remove();
556
+ this.aiCodeCompletion = undefined;
557
+ }
558
+ }
559
+ }
560
+
535
561
  async onAiCodeCompletionTeaserActionKeyDown(event: Event): Promise<void> {
536
562
  if (this.teaser?.isShowing()) {
537
563
  await this.teaser?.onAction(event);
@@ -40,6 +40,7 @@ import * as Platform from '../../core/platform/platform.js';
40
40
  import * as Root from '../../core/root/root.js';
41
41
  import * as SDK from '../../core/sdk/sdk.js';
42
42
  import * as Protocol from '../../generated/protocol.js';
43
+ import * as Badges from '../../models/badges/badges.js';
43
44
  import type * as Elements from '../../models/elements/elements.js';
44
45
  import type * as IssuesManager from '../../models/issues_manager/issues_manager.js';
45
46
  import * as TextUtils from '../../models/text_utils/text_utils.js';
@@ -1603,6 +1604,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1603
1604
 
1604
1605
  if (attributeName !== null && (attributeName.trim() || newText.trim()) && oldText !== newText) {
1605
1606
  this.nodeInternal.setAttribute(attributeName, newText, moveToNextAttributeIfNeeded.bind(this));
1607
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.DOM_ELEMENT_OR_ATTRIBUTE_EDITED);
1606
1608
  return;
1607
1609
  }
1608
1610
 
@@ -1661,6 +1663,8 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1661
1663
  if (!treeOutline) {
1662
1664
  return;
1663
1665
  }
1666
+
1667
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.DOM_ELEMENT_OR_ATTRIBUTE_EDITED);
1664
1668
  const newTreeItem = treeOutline.selectNodeAfterEdit(wasExpanded, error, newNode);
1665
1669
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
1666
1670
  // @ts-expect-error
@@ -2581,6 +2585,9 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2581
2585
  const adorner = this.adorn(config);
2582
2586
  const onClick = async(): Promise<void> => {
2583
2587
  const {nodeIds} = await node.domModel().agent.invoke_forceShowPopover({nodeId, enable: adorner.isActive()});
2588
+ if (adorner.isActive()) {
2589
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.MODERN_DOM_BADGE_CLICKED);
2590
+ }
2584
2591
  for (const closedPopoverNodeId of nodeIds) {
2585
2592
  const node = this.node().domModel().nodeForId(closedPopoverNodeId);
2586
2593
  const treeElement = node && this.treeOutline?.treeElementByNode.get(node);
@@ -2617,6 +2624,9 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2617
2624
  const onClick = ((() => {
2618
2625
  if (adorner.isActive()) {
2619
2626
  node.domModel().overlayModel().highlightGridInPersistentOverlay(nodeId);
2627
+ if (isSubgrid) {
2628
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.MODERN_DOM_BADGE_CLICKED);
2629
+ }
2620
2630
  } else {
2621
2631
  node.domModel().overlayModel().hideGridInPersistentOverlay(nodeId);
2622
2632
  }
@@ -2658,6 +2668,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2658
2668
  const onClick = ((() => {
2659
2669
  if (adorner.isActive()) {
2660
2670
  node.domModel().overlayModel().highlightGridInPersistentOverlay(nodeId);
2671
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.MODERN_DOM_BADGE_CLICKED);
2661
2672
  } else {
2662
2673
  node.domModel().overlayModel().hideGridInPersistentOverlay(nodeId);
2663
2674
  }
@@ -2786,6 +2797,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2786
2797
  const model = node.domModel().overlayModel();
2787
2798
  if (adorner.isActive()) {
2788
2799
  model.highlightContainerQueryInPersistentOverlay(nodeId);
2800
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.MODERN_DOM_BADGE_CLICKED);
2789
2801
  } else {
2790
2802
  model.hideContainerQueryInPersistentOverlay(nodeId);
2791
2803
  }
@@ -38,6 +38,7 @@ import * as Common from '../../core/common/common.js';
38
38
  import * as i18n from '../../core/i18n/i18n.js';
39
39
  import * as Root from '../../core/root/root.js';
40
40
  import * as SDK from '../../core/sdk/sdk.js';
41
+ import * as Badges from '../../models/badges/badges.js';
41
42
  import * as Elements from '../../models/elements/elements.js';
42
43
  import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
43
44
  import * as CodeHighlighter from '../../ui/components/code_highlighter/code_highlighter.js';
@@ -1332,6 +1333,8 @@ export class ElementsTreeOutline extends
1332
1333
  return;
1333
1334
  }
1334
1335
 
1336
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.DOM_ELEMENT_OR_ATTRIBUTE_EDITED);
1337
+
1335
1338
  // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
1336
1339
  this.runPendingUpdates();
1337
1340
 
@@ -41,6 +41,7 @@ import * as Platform from '../../core/platform/platform.js';
41
41
  import * as Root from '../../core/root/root.js';
42
42
  import * as SDK from '../../core/sdk/sdk.js';
43
43
  import * as Protocol from '../../generated/protocol.js';
44
+ import * as Badges from '../../models/badges/badges.js';
44
45
  import * as Bindings from '../../models/bindings/bindings.js';
45
46
  import * as TextUtils from '../../models/text_utils/text_utils.js';
46
47
  import * as Buttons from '../../ui/components/buttons/buttons.js';
@@ -1546,6 +1547,8 @@ export class StylePropertiesSection {
1546
1547
  if (!success) {
1547
1548
  return Promise.resolve();
1548
1549
  }
1550
+
1551
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.CSS_RULE_MODIFIED);
1549
1552
  return this.matchedStyles.recomputeMatchingSelectors(rule).then(updateSourceRanges.bind(this, rule));
1550
1553
  }
1551
1554
 
@@ -10,6 +10,7 @@ import * as i18n from '../../core/i18n/i18n.js';
10
10
  import * as Platform from '../../core/platform/platform.js';
11
11
  import * as Root from '../../core/root/root.js';
12
12
  import * as SDK from '../../core/sdk/sdk.js';
13
+ import * as Badges from '../../models/badges/badges.js';
13
14
  import * as Bindings from '../../models/bindings/bindings.js';
14
15
  import * as TextUtils from '../../models/text_utils/text_utils.js';
15
16
  import type * as CodeMirror from '../../third_party/codemirror.next/codemirror.next.js';
@@ -3292,6 +3293,10 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
3292
3293
 
3293
3294
  const overwriteProperty = !this.newProperty || hasBeenEditedIncrementally;
3294
3295
  let success: boolean = await this.property.setText(styleText, majorChange, overwriteProperty);
3296
+ if (success && majorChange) {
3297
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.CSS_RULE_MODIFIED);
3298
+ }
3299
+
3295
3300
  // Revert to the original text if applying the new text failed
3296
3301
  if (hasBeenEditedIncrementally && majorChange && !success) {
3297
3302
  majorChange = false;
@@ -336,19 +336,13 @@ export class GenericSettingsTab extends UI.Widget.VBox implements SettingsTab {
336
336
  this.#updateSyncSectionTimerId = -1;
337
337
  }
338
338
 
339
- void Promise
340
- .all([
341
- new Promise<Host.InspectorFrontendHostAPI.SyncInformation>(
342
- resolve => Host.InspectorFrontendHost.InspectorFrontendHostInstance.getSyncInformation(resolve)),
343
- Root.Runtime.hostConfig.devToolsGdpProfiles?.enabled ? Host.GdpClient.GdpClient.instance().getProfile() :
344
- Promise.resolve(undefined),
345
- ])
346
- .then(([syncInfo, gdpProfile]) => {
339
+ void new Promise<Host.InspectorFrontendHostAPI.SyncInformation>(
340
+ resolve => Host.InspectorFrontendHost.InspectorFrontendHostInstance.getSyncInformation(resolve))
341
+ .then(syncInfo => {
347
342
  this.syncSection.data = {
348
343
  syncInfo,
349
344
  syncSetting: Common.Settings.moduleSetting('sync-preferences') as Common.Settings.Setting<boolean>,
350
345
  receiveBadgesSetting: Common.Settings.Settings.instance().moduleSetting('receive-gdp-badges'),
351
- gdpProfile: gdpProfile ?? undefined,
352
346
  };
353
347
  if (!syncInfo.isSyncActive || !syncInfo.arePreferencesSynced) {
354
348
  this.#updateSyncSectionTimerId = window.setTimeout(this.updateSyncSection.bind(this), 500);
@@ -117,7 +117,6 @@ export interface SyncSectionData {
117
117
  syncInfo: Host.InspectorFrontendHostAPI.SyncInformation;
118
118
  syncSetting: Common.Settings.Setting<boolean>;
119
119
  receiveBadgesSetting: Common.Settings.Setting<boolean>;
120
- gdpProfile?: Host.GdpClient.Profile;
121
120
  }
122
121
 
123
122
  export class SyncSection extends HTMLElement {
@@ -132,7 +131,7 @@ export class SyncSection extends HTMLElement {
132
131
  this.#syncInfo = data.syncInfo;
133
132
  this.#syncSetting = data.syncSetting;
134
133
  this.#receiveBadgesSetting = data.receiveBadgesSetting;
135
- this.#gdpProfile = data.gdpProfile;
134
+ void this.#updateGdpProfile();
136
135
  void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
137
136
  }
138
137
 
@@ -157,6 +156,11 @@ export class SyncSection extends HTMLElement {
157
156
  `, this.#shadow, {host: this});
158
157
  // clang-format on
159
158
  }
159
+
160
+ async #updateGdpProfile(): Promise<void> {
161
+ this.#gdpProfile = await Host.GdpClient.GdpClient.instance().getProfile() ?? undefined;
162
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
163
+ }
160
164
  }
161
165
 
162
166
  function renderSettingCheckboxIfNeeded(
@@ -22,6 +22,8 @@ const CITATIONS_TOOLTIP_ID = 'sources-ai-code-completion-citations-tooltip';
22
22
 
23
23
  export class AiCodeCompletionPlugin extends Plugin {
24
24
  #aidaClient?: Host.AidaClient.AidaClient;
25
+ #aidaAvailability?: Host.AidaClient.AidaAccessPreconditions;
26
+ #boundOnAidaAvailabilityChange: () => Promise<void>;
25
27
  #aiCodeCompletion?: AiCodeCompletion.AiCodeCompletion.AiCodeCompletion;
26
28
  #aiCodeCompletionSetting = Common.Settings.Settings.instance().createSetting('ai-code-completion-enabled', false);
27
29
  #aiCodeCompletionTeaserDismissedSetting =
@@ -48,6 +50,10 @@ export class AiCodeCompletionPlugin extends Plugin {
48
50
  }
49
51
  this.#boundEditorKeyDown = this.#editorKeyDown.bind(this);
50
52
  this.#boundOnAiCodeCompletionSettingChanged = this.#onAiCodeCompletionSettingChanged.bind(this);
53
+ this.#boundOnAidaAvailabilityChange = this.#onAidaAvailabilityChange.bind(this);
54
+ Host.AidaClient.HostConfigTracker.instance().addEventListener(
55
+ Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED, this.#boundOnAidaAvailabilityChange);
56
+ void this.#onAidaAvailabilityChange();
51
57
  const showTeaser = !this.#aiCodeCompletionSetting.get() && !this.#aiCodeCompletionTeaserDismissedSetting.get();
52
58
  if (showTeaser) {
53
59
  this.#teaser = new PanelCommon.AiCodeCompletionTeaser({onDetach: this.#detachAiCodeCompletionTeaser.bind(this)});
@@ -61,6 +67,8 @@ export class AiCodeCompletionPlugin extends Plugin {
61
67
  override dispose(): void {
62
68
  this.#teaser = undefined;
63
69
  this.#aiCodeCompletionSetting.removeChangeListener(this.#boundOnAiCodeCompletionSettingChanged);
70
+ Host.AidaClient.HostConfigTracker.instance().removeEventListener(
71
+ Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED, this.#boundOnAidaAvailabilityChange);
64
72
  this.#editor?.removeEventListener('keydown', this.#boundEditorKeyDown);
65
73
  this.#cleanupAiCodeCompletion();
66
74
  super.dispose();
@@ -203,23 +211,31 @@ export class AiCodeCompletionPlugin extends Plugin {
203
211
  this.#detachAiCodeCompletionTeaser();
204
212
  this.#teaser = undefined;
205
213
  }
206
- this.#aiCodeCompletion = new AiCodeCompletion.AiCodeCompletion.AiCodeCompletion(
207
- {aidaClient: this.#aidaClient}, this.#editor, AiCodeCompletion.AiCodeCompletion.Panel.SOURCES);
208
- this.#aiCodeCompletion.addEventListener(
209
- AiCodeCompletion.AiCodeCompletion.Events.REQUEST_TRIGGERED, this.#onAiRequestTriggered, this);
210
- this.#aiCodeCompletion.addEventListener(
211
- AiCodeCompletion.AiCodeCompletion.Events.RESPONSE_RECEIVED, this.#onAiResponseReceived, this);
214
+ if (!this.#aiCodeCompletion) {
215
+ this.#aiCodeCompletion = new AiCodeCompletion.AiCodeCompletion.AiCodeCompletion(
216
+ {aidaClient: this.#aidaClient}, this.#editor, AiCodeCompletion.AiCodeCompletion.Panel.SOURCES);
217
+ this.#aiCodeCompletion.addEventListener(
218
+ AiCodeCompletion.AiCodeCompletion.Events.REQUEST_TRIGGERED, this.#onAiRequestTriggered, this);
219
+ this.#aiCodeCompletion.addEventListener(
220
+ AiCodeCompletion.AiCodeCompletion.Events.RESPONSE_RECEIVED, this.#onAiResponseReceived, this);
221
+ }
212
222
  this.#createAiCodeCompletionDisclaimer();
213
223
  this.#createAiCodeCompletionCitationsToolbar();
214
224
  }
215
225
 
216
226
  #createAiCodeCompletionDisclaimer(): void {
227
+ if (this.#aiCodeCompletionDisclaimer) {
228
+ return;
229
+ }
217
230
  this.#aiCodeCompletionDisclaimer = new PanelCommon.AiCodeCompletionDisclaimer();
218
231
  this.#aiCodeCompletionDisclaimer.disclaimerTooltipId = DISCLAIMER_TOOLTIP_ID;
219
232
  this.#aiCodeCompletionDisclaimer.show(this.#aiCodeCompletionDisclaimerContainer, undefined, true);
220
233
  }
221
234
 
222
235
  #createAiCodeCompletionCitationsToolbar(): void {
236
+ if (this.#aiCodeCompletionCitationsToolbar) {
237
+ return;
238
+ }
223
239
  this.#aiCodeCompletionCitationsToolbar =
224
240
  new PanelCommon.AiCodeCompletionSummaryToolbar({citationsTooltipId: CITATIONS_TOOLTIP_ID, hasTopBorder: true});
225
241
  this.#aiCodeCompletionCitationsToolbar.show(this.#aiCodeCompletionCitationsToolbarContainer, undefined, true);
@@ -253,12 +269,25 @@ export class AiCodeCompletionPlugin extends Plugin {
253
269
  }
254
270
  }
255
271
 
272
+ async #onAidaAvailabilityChange(): Promise<void> {
273
+ const currentAidaAvailability = await Host.AidaClient.AidaClient.checkAccessPreconditions();
274
+ if (currentAidaAvailability !== this.#aidaAvailability) {
275
+ this.#aidaAvailability = currentAidaAvailability;
276
+ if (this.#aidaAvailability === Host.AidaClient.AidaAccessPreconditions.AVAILABLE) {
277
+ this.#onAiCodeCompletionSettingChanged();
278
+ } else if (this.#aiCodeCompletion) {
279
+ this.#cleanupAiCodeCompletion();
280
+ }
281
+ }
282
+ }
283
+
256
284
  #cleanupAiCodeCompletion(): void {
257
285
  this.#aiCodeCompletion?.removeEventListener(
258
286
  AiCodeCompletion.AiCodeCompletion.Events.REQUEST_TRIGGERED, this.#onAiRequestTriggered, this);
259
287
  this.#aiCodeCompletion?.removeEventListener(
260
288
  AiCodeCompletion.AiCodeCompletion.Events.RESPONSE_RECEIVED, this.#onAiResponseReceived, this);
261
289
  this.#aiCodeCompletion?.remove();
290
+ this.#aiCodeCompletion = undefined;
262
291
  this.#aiCodeCompletionCitations = [];
263
292
  this.#aiCodeCompletionDisclaimerContainer.removeChildren();
264
293
  this.#aiCodeCompletionDisclaimer = undefined;
@@ -1534,7 +1534,7 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
1534
1534
  // extensions sourcemaps provide little to no-value for the exported trace
1535
1535
  // debugging, so they are filtered out.
1536
1536
  return metadata.sourceMaps.filter(value => {
1537
- return value.url && Trace.Helpers.Trace.isExtensionUrl(value.url);
1537
+ return !Trace.Helpers.Trace.isExtensionUrl(value.url);
1538
1538
  });
1539
1539
  }
1540
1540
 
@@ -2733,14 +2733,27 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
2733
2733
  async #executeNewTrace(
2734
2734
  collectedEvents: Trace.Types.Events.Event[], isFreshRecording: boolean,
2735
2735
  metadata: Trace.Types.File.MetaData|null): Promise<void> {
2736
- await this.#traceEngineModel.parse(
2737
- collectedEvents,
2738
- {
2739
- metadata: metadata ?? undefined,
2740
- isFreshRecording,
2741
- resolveSourceMap: this.#createSourceMapResolver(isFreshRecording, metadata),
2736
+ const config: Trace.Types.Configuration.ParseOptions = {
2737
+ metadata: metadata ?? undefined,
2738
+ isFreshRecording,
2739
+ resolveSourceMap: this.#createSourceMapResolver(isFreshRecording, metadata),
2740
+ };
2741
+
2742
+ if (window.location.href.includes('devtools/bundled') || window.location.search.includes('debugFrontend')) {
2743
+ // Someone is debugging DevTools, enable the logger to give timings
2744
+ // when tracing the performance panel itself.
2745
+ const times: Record<string, number> = {};
2746
+ config.logger = {
2747
+ start(id) {
2748
+ times[id] = performance.now();
2742
2749
  },
2743
- );
2750
+ end(id) {
2751
+ performance.measure(id, {start: times[id]});
2752
+ },
2753
+ };
2754
+ }
2755
+
2756
+ await this.#traceEngineModel.parse(collectedEvents, config);
2744
2757
 
2745
2758
  // Store all source maps on the trace metadata.
2746
2759
  // If not fresh, we can't validate the maps are still accurate.
@@ -3039,8 +3052,7 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
3039
3052
  for (const modelName in insightsForNav.model) {
3040
3053
  const model = modelName as keyof Trace.Insights.Types.InsightModelsType;
3041
3054
  const insight = insightsForNav.model[model];
3042
- const formatter = new AiAssistanceModel.PerformanceInsightFormatter(
3043
- AiAssistanceModel.PERF_AGENT_UNIT_FORMATTERS, parsedTrace, insight);
3055
+ const formatter = new AiAssistanceModel.PerformanceInsightFormatter(parsedTrace, insight);
3044
3056
  if (!formatter.insightIsSupported()) {
3045
3057
  // Not all Insights are integrated with "Ask AI" yet, let's avoid
3046
3058
  // filling up the response with those ones because there will be no
@@ -476,6 +476,9 @@ const UIStrings = {
476
476
  const str_ = i18n.i18n.registerUIStrings('panels/timeline/TimelineUIUtils.ts', UIStrings);
477
477
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
478
478
 
479
+ // Look for scheme:// plus text and exclude any punctuation at the end.
480
+ export const URL_REGEX = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/)[^\s"]{2,}[^\s"'\)\}\],:;.!?]/u;
481
+
479
482
  let eventDispatchDesciptors: EventDispatchTypeDescriptor[];
480
483
 
481
484
  let colorGenerator: Common.Color.Generator;
@@ -897,9 +900,7 @@ export class TimelineUIUtils {
897
900
  * of the link is the URL, so the visible string to the user is unchanged.
898
901
  */
899
902
  static parseStringForLinks(rawString: string): DocumentFragment {
900
- // Look for scheme:// plus text and exclude any punctuation at the end.
901
- const urlRegex = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/)[^\s"]{2,}[^\s"'\)\}\],:;.!?]/u;
902
- const results = TextUtils.TextUtils.Utils.splitStringByRegexes(rawString, [urlRegex]);
903
+ const results = TextUtils.TextUtils.Utils.splitStringByRegexes(rawString, [URL_REGEX]);
903
904
  const nodes = results.map(result => {
904
905
  if (result.regexIndex === -1) {
905
906
  return result.value;
@@ -126,22 +126,3 @@ export class AIQueries {
126
126
  .filter(tree => !!tree);
127
127
  }
128
128
  }
129
-
130
- /**
131
- * Calculates the trace bounds for the given insight that are relevant.
132
- *
133
- * Uses the insight's overlays to determine the relevant trace bounds. If there are
134
- * no overlays, falls back to the insight set's navigation bounds.
135
- */
136
- export function insightBounds(
137
- insight: Trace.Insights.Types.InsightModel,
138
- insightSetBounds: Trace.Types.Timing.TraceWindowMicro): Trace.Types.Timing.TraceWindowMicro {
139
- const overlays = insight.createOverlays?.() ?? [];
140
- const windows = overlays.map(Trace.Helpers.Timing.traceWindowFromOverlay).filter(bounds => !!bounds);
141
- const overlaysBounds = Trace.Helpers.Timing.combineTraceWindowsMicro(windows);
142
- if (overlaysBounds) {
143
- return overlaysBounds;
144
- }
145
-
146
- return insightSetBounds;
147
- }
@@ -171,7 +171,7 @@
171
171
  color: var(--sys-color-on-surface-subtle);
172
172
 
173
173
  .toolbar-dropdown-arrow {
174
- top: var(--sys-size-2);
174
+ top: var(--sys-size-1);
175
175
  }
176
176
 
177
177
  &:hover {
@@ -780,7 +780,7 @@ devtools-toolbar {
780
780
  .toolbar-dropdown-arrow {
781
781
  pointer-events: none;
782
782
  flex: none;
783
- top: 2px;
783
+ top: var(--sys-size-1);
784
784
  }
785
785
 
786
786
  .toolbar-button.dark-text .toolbar-dropdown-arrow {
@@ -22,7 +22,7 @@ button.soft-dropdown {
22
22
  }
23
23
 
24
24
  devtools-icon {
25
- top: var(--sys-size-2);
25
+ top: var(--sys-size-1);
26
26
  }
27
27
  }
28
28
 
package/package.json CHANGED
@@ -102,5 +102,5 @@
102
102
  "@eslint/core": "0.15.1"
103
103
  }
104
104
  },
105
- "version": "1.0.1510848"
105
+ "version": "1.0.1512349"
106
106
  }
@@ -1,9 +0,0 @@
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
- export interface UnitFormatters {
6
- millis: (x: number) => string;
7
- micros: (x: number) => string;
8
- bytes: (x: number) => string;
9
- }