chrome-devtools-frontend 1.0.1581449 → 1.0.1581708

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 (54) hide show
  1. package/agents/prompts/merging-devtools-module.md +144 -0
  2. package/agents/prompts/ui-widgets.md +351 -0
  3. package/agents/prompts/verification.md +2 -1
  4. package/docs/contributing/README.md +5 -6
  5. package/docs/contributing/changes.md +1 -2
  6. package/docs/styleguide/ux/README.md +1 -1
  7. package/front_end/core/sdk/OverlayModel.ts +4 -2
  8. package/front_end/core/sdk/StorageKeyManager.ts +6 -1
  9. package/front_end/core/sdk/Target.ts +4 -2
  10. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +22 -16
  11. package/front_end/entrypoints/greendev_floaty/floaty.css +41 -1
  12. package/front_end/entrypoints/greendev_floaty/floaty.html +8 -1
  13. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +4 -4
  14. package/front_end/entrypoints/node_app/app/NodeMain.ts +19 -1
  15. package/front_end/entrypoints/node_app/node_app.ts +34 -0
  16. package/front_end/models/ai_assistance/AiConversation.ts +10 -0
  17. package/front_end/models/ai_assistance/agents/AiAgent.ts +2 -0
  18. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +22 -0
  19. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +71 -1
  20. package/front_end/models/computed_style/ComputedStyleModel.ts +26 -0
  21. package/front_end/models/issues_manager/CookieIssue.ts +0 -28
  22. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +87 -6
  23. package/front_end/panels/ai_assistance/components/ChatInput.ts +1 -1
  24. package/front_end/panels/application/ApplicationPanelSidebar.ts +13 -11
  25. package/front_end/panels/application/DOMStorageModel.ts +1 -1
  26. package/front_end/panels/application/ResourcesPanel.ts +10 -5
  27. package/front_end/panels/common/AiCodeCompletionTeaser.ts +13 -3
  28. package/front_end/panels/common/aiCodeCompletionTeaser.css +6 -0
  29. package/front_end/panels/console_counters/WarningErrorCounter.ts +16 -10
  30. package/front_end/panels/elements/ComputedStyleWidget.ts +55 -37
  31. package/front_end/panels/elements/PlatformFontsWidget.ts +23 -10
  32. package/front_end/panels/greendev/GreenDevPanel.css +42 -1
  33. package/front_end/panels/greendev/GreenDevPanel.ts +30 -1
  34. package/front_end/panels/network/RequestInitiatorView.ts +8 -11
  35. package/front_end/panels/network/RequestTimingView.ts +1 -1
  36. package/front_end/panels/settings/KeybindsSettingsTab.ts +4 -3
  37. package/front_end/panels/sources/OutlineQuickOpen.ts +19 -0
  38. package/front_end/panels/timeline/TimelinePanel.ts +25 -0
  39. package/front_end/third_party/lighthouse/README.chromium +2 -2
  40. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +145 -144
  41. package/front_end/third_party/lighthouse/report/bundle.js +12 -5
  42. package/front_end/third_party/lighthouse/report-assets/report-generator.mjs +2 -2
  43. package/front_end/ui/legacy/ListControl.ts +28 -1
  44. package/front_end/ui/legacy/Treeoutline.ts +1 -1
  45. package/front_end/ui/legacy/UIUtils.ts +17 -7
  46. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +4 -2
  47. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  48. package/inspector_overlay/main.ts +18 -3
  49. package/inspector_overlay/tool_green_dev_anchors.css +54 -0
  50. package/inspector_overlay/tool_green_dev_anchors.ts +164 -0
  51. package/inspector_overlay/tool_persistent.ts +14 -0
  52. package/package.json +1 -1
  53. package/docs/contributing/design.md +0 -166
  54. package/docs/design_guidelines.md +0 -1
@@ -37,9 +37,13 @@ export class ResourcesPanel extends UI.Panel.PanelWithSidebar {
37
37
  private cookieView: CookieItemsView|null;
38
38
  private deviceBoundSessionsView: DeviceBoundSessionsView|null;
39
39
  private readonly sidebar: ApplicationPanelSidebar;
40
+ mode: 'default'|'node' = 'default';
40
41
 
41
- private constructor() {
42
+ private constructor(
43
+ mode: 'default'|'node' = 'default',
44
+ ) {
42
45
  super('resources');
46
+ this.mode = mode;
43
47
  this.registerRequiredCSS(resourcesPanelStyles);
44
48
 
45
49
  this.resourcesLastSelectedItemSetting =
@@ -68,11 +72,12 @@ export class ResourcesPanel extends UI.Panel.PanelWithSidebar {
68
72
  }
69
73
 
70
74
  static instance(opts: {
71
- forceNew: boolean|null,
72
- } = {forceNew: null}): ResourcesPanel {
73
- const {forceNew} = opts;
75
+ forceNew?: boolean|null,
76
+ mode?: 'default'|'node',
77
+ } = {forceNew: null, mode: 'default'}): ResourcesPanel {
78
+ const {forceNew, mode} = opts;
74
79
  if (!resourcesPanelInstance || forceNew) {
75
- resourcesPanelInstance = new ResourcesPanel();
80
+ resourcesPanelInstance = new ResourcesPanel(mode);
76
81
  }
77
82
 
78
83
  return resourcesPanelInstance;
@@ -73,12 +73,12 @@ const UIStringsNotTranslate = {
73
73
  * @description Code generation disclaimer item text for the fre dialog.
74
74
  */
75
75
  freDisclaimerDescribeCodeInComment:
76
- 'In Console or Sources, describe the code you need in a comment, then press Ctrl+I to generate it.',
76
+ 'In Console or Sources, describe the code you need in a comment, then press ctrl+i to generate it.',
77
77
  /**
78
78
  * @description Code generation disclaimer item text for the fre dialog.
79
79
  */
80
80
  freDisclaimerDescribeCodeInCommentForMacOs:
81
- 'In Console or Sources, describe the code you need in a comment, then press Cmd+I to generate it.',
81
+ 'In Console or Sources, describe the code you need in a comment, then press cmd+i to generate it.',
82
82
  /**
83
83
  * @description Privacy disclaimer item text for the fre dialog.
84
84
  */
@@ -140,7 +140,17 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
140
140
  <span>${lockedString(UIStringsNotTranslate.i)}</span>
141
141
  </span>
142
142
  </span>&nbsp;${lockedString(UIStringsNotTranslate.toTurnOnCodeSuggestions)}&nbsp;
143
- <span role="button" class="ai-code-completion-teaser-dismiss" @click=${input.onDismiss}
143
+ <span role="button" class="ai-code-completion-teaser-dismiss"
144
+ tabindex="0"
145
+ @click=${input.onDismiss}
146
+ @keydown=${(e: KeyboardEvent) => {
147
+ // Handle Enter and Space events to make dismiss button accessible for only keyboard users.
148
+ if (e.key === 'Enter' || e.key === ' ') {
149
+ input.onDismiss(e);
150
+ e.stopPropagation();
151
+ e.preventDefault();
152
+ }
153
+ }}
144
154
  jslog=${VisualLogging.action('ai-code-completion-teaser.dismiss').track({click: true})}>
145
155
  ${lockedString(UIStringsNotTranslate.dontShowAgain)}
146
156
  </span>
@@ -26,6 +26,12 @@
26
26
  .ai-code-completion-teaser-dismiss {
27
27
  text-decoration: underline;
28
28
  cursor: pointer;
29
+
30
+ &:focus-visible {
31
+ border-radius: var(--sys-shape-corner-extra-small);
32
+ outline: var(--sys-size-2) solid var(--sys-color-state-focus-ring);
33
+ outline-offset: 0;
34
+ }
29
35
  }
30
36
 
31
37
  .ai-code-completion-teaser-action {
@@ -12,9 +12,11 @@ import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
12
12
  import type * as IconButton from '../../ui/components/icon_button/icon_button.js';
13
13
  import * as IssueCounter from '../../ui/components/issue_counter/issue_counter.js';
14
14
  import * as UI from '../../ui/legacy/legacy.js';
15
- import {html, nothing, render} from '../../ui/lit/lit.js';
15
+ import * as Lit from '../../ui/lit/lit.js';
16
16
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
17
17
 
18
+ const {html, nothing, render} = Lit;
19
+
18
20
  const UIStrings = {
19
21
  /**
20
22
  * @description The console error count in the Warning Error Counter shown in the main toolbar (top-left in DevTools). The error count refers to the number of errors currently present in the JavaScript console.
@@ -95,21 +97,25 @@ const DEFAULT_VIEW: View = (input, _output, target) => {
95
97
 
96
98
  render(
97
99
  html`<div class="status-buttons"
98
- ><icon-button
100
+ >${
101
+ errors + warnings ? html`<icon-button
99
102
  .data=${iconData}
100
103
  title=${consoleTitle}
101
- class=${'small' + warnings || errors ? nothing as unknown as string : 'hidden'}
104
+ class=${'small'}
102
105
  jslog=${VisualLogging.counter('console').track({
103
- click: true
104
- })}
105
- ></icon-button><devtools-issue-counter
106
- class=${'main-toolbar' + (issues ? '' : ' hidden')}
106
+ click: true
107
+ })}
108
+ ></icon-button>` :
109
+ nothing}${
110
+ issues ? html`<devtools-issue-counter
111
+ class=${'main-toolbar'}
107
112
  title=${issuesTitle}
108
113
  .data=${issueCounterData}
109
114
  jslog=${VisualLogging.counter('issue').track({
110
- click: true
111
- })}
112
- ></devtools-issue-counter></div>`,
115
+ click: true
116
+ })}
117
+ ></devtools-issue-counter>` :
118
+ nothing}</div>`,
113
119
  target);
114
120
  };
115
121
 
@@ -53,7 +53,7 @@ import {categorizePropertyName, type Category, DefaultCategoryOrder} from './Pro
53
53
  import {Renderer, rendererBase, type RenderingContext, StringRenderer, URLRenderer} from './PropertyRenderer.js';
54
54
  import {StylePropertiesSection} from './StylePropertiesSection.js';
55
55
 
56
- const {html} = Lit;
56
+ const {html, render} = Lit;
57
57
 
58
58
  const UIStrings = {
59
59
  /**
@@ -197,7 +197,7 @@ const createTraceElement =
197
197
  return trace;
198
198
  };
199
199
 
200
- /** clang-format off **/
200
+ // clang-format off
201
201
  class ColorRenderer extends rendererBase(SDK.CSSPropertyParserMatchers.ColorMatch) {
202
202
  // clang-format on
203
203
  override render(match: SDK.CSSPropertyParserMatchers.ColorMatch, context: RenderingContext): Node[] {
@@ -261,21 +261,47 @@ type ComputedStyleData = {
261
261
  name: string,
262
262
  };
263
263
 
264
+ interface ComputedStyleWidgetInput {
265
+ computedStylesTree: TreeOutline.TreeOutline.TreeOutline<ComputedStyleData>;
266
+ toolbar: HTMLElement;
267
+ hasMatches: boolean;
268
+ computedStyleModel: ComputedStyleModule.ComputedStyleModel.ComputedStyleModel;
269
+ }
270
+
271
+ type View = (input: ComputedStyleWidgetInput, output: null, target: HTMLElement) => void;
272
+
273
+ export const DEFAULT_VIEW: View = (input, _output, target) => {
274
+ // clang-format off
275
+ render(html`
276
+ <div class="styles-sidebar-pane-toolbar">${input.toolbar}</div>
277
+ <div class="computed-style-tree-outline-container">
278
+ ${input.computedStylesTree}
279
+ </div>
280
+ ${!input.hasMatches ? html`<div class="gray-info-message">${i18nString(UIStrings.noMatchingProperty)}</div>` : ''}
281
+ <div class="platform-fonts-widget-container">
282
+ <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(PlatformFontsWidget, { sharedModel: input.computedStyleModel})}></devtools-widget>
283
+ </div>
284
+ `, target);
285
+ // clang-format on
286
+ };
287
+
264
288
  export class ComputedStyleWidget extends UI.Widget.VBox {
265
289
  private computedStyleModel: ComputedStyleModule.ComputedStyleModel.ComputedStyleModel;
266
290
  private readonly showInheritedComputedStylePropertiesSetting: Common.Settings.Setting<boolean>;
267
291
  private readonly groupComputedStylesSetting: Common.Settings.Setting<boolean>;
268
292
  input: UI.Toolbar.ToolbarInput;
269
293
  private filterRegex: RegExp|null;
270
- private readonly noMatchesElement: HTMLElement;
271
294
  private readonly linkifier: Components.Linkifier.Linkifier;
272
295
  private readonly imagePreviewPopover: ImagePreviewPopover;
273
296
 
274
297
  #computedStylesTree = new TreeOutline.TreeOutline.TreeOutline<ComputedStyleData>();
275
298
  #treeData?: TreeOutline.TreeOutline.TreeOutlineData<ComputedStyleData>;
299
+ readonly #view: View;
300
+ private readonly toolbarElement: HTMLElement;
276
301
 
277
302
  constructor(computedStyleModel: ComputedStyleModule.ComputedStyleModel.ComputedStyleModel) {
278
303
  super({useShadowDom: true});
304
+ this.#view = DEFAULT_VIEW;
279
305
  this.registerRequiredCSS(computedStyleSidebarPaneStyles);
280
306
 
281
307
  this.contentElement.classList.add('styles-sidebar-computed-style-widget');
@@ -295,8 +321,12 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
295
321
  this.requestUpdate();
296
322
  });
297
323
 
298
- const hbox = this.contentElement.createChild('div', 'hbox styles-sidebar-pane-toolbar');
299
- const toolbar = hbox.createChild('devtools-toolbar', 'styles-pane-toolbar');
324
+ this.toolbarElement = document.createElement('div');
325
+ this.toolbarElement.classList.add('hbox', 'styles-sidebar-pane-toolbar');
326
+ const toolbar = document.createElement('devtools-toolbar');
327
+ toolbar.classList.add('styles-pane-toolbar');
328
+ this.toolbarElement.appendChild(toolbar);
329
+
300
330
  const filterInput = new UI.Toolbar.ToolbarFilter(undefined, 1, 1, undefined, undefined, false);
301
331
  filterInput.addEventListener(UI.Toolbar.ToolbarInput.Event.TEXT_CHANGED, this.onFilterChanged, this);
302
332
  toolbar.appendToolbarItem(filterInput);
@@ -308,11 +338,6 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
308
338
  toolbar.appendToolbarItem(
309
339
  new UI.Toolbar.ToolbarSettingCheckbox(this.groupComputedStylesSetting, undefined, i18nString(UIStrings.group)));
310
340
 
311
- this.noMatchesElement = this.contentElement.createChild('div', 'gray-info-message');
312
- this.noMatchesElement.textContent = i18nString(UIStrings.noMatchingProperty);
313
-
314
- this.contentElement.appendChild(this.#computedStylesTree);
315
-
316
341
  this.linkifier = new Components.Linkifier.Linkifier(maxLinkLength);
317
342
 
318
343
  this.imagePreviewPopover = new ImagePreviewPopover(this.contentElement, event => {
@@ -323,8 +348,7 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
323
348
  return null;
324
349
  }, () => this.computedStyleModel.node);
325
350
 
326
- const fontsWidget = new PlatformFontsWidget(this.computedStyleModel);
327
- fontsWidget.show(this.contentElement);
351
+ this.#updateView({hasMatches: true});
328
352
  }
329
353
 
330
354
  override onResize(): void {
@@ -342,11 +366,24 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
342
366
  UI.Context.Context.instance().setFlavor(ComputedStyleWidget, null);
343
367
  }
344
368
 
369
+ /**
370
+ * @param input.hasMatches Whether any properties matched the current filter (or if any properties exist at all).
371
+ */
372
+ #updateView({hasMatches}: {hasMatches: boolean}): void {
373
+ this.#view(
374
+ {
375
+ computedStylesTree: this.#computedStylesTree,
376
+ toolbar: this.toolbarElement,
377
+ hasMatches,
378
+ computedStyleModel: this.computedStyleModel,
379
+ },
380
+ null, this.contentElement);
381
+ }
382
+
345
383
  override async performUpdate(): Promise<void> {
346
- const [nodeStyles, matchedStyles] =
347
- await Promise.all([this.computedStyleModel.fetchComputedStyle(), this.fetchMatchedCascade()]);
384
+ const {computedStyle: nodeStyles, matchedStyles} = await this.computedStyleModel.fetchAllComputedStyleInfo();
348
385
  if (!nodeStyles || !matchedStyles) {
349
- this.noMatchesElement.classList.remove('hidden');
386
+ this.#updateView({hasMatches: false});
350
387
  return;
351
388
  }
352
389
  const shouldGroupComputedStyles = this.groupComputedStylesSetting.get();
@@ -357,25 +394,6 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
357
394
  }
358
395
  }
359
396
 
360
- private async fetchMatchedCascade(): Promise<SDK.CSSMatchedStyles.CSSMatchedStyles|null> {
361
- const node = this.computedStyleModel.node;
362
- if (!node || !this.computedStyleModel.cssModel()) {
363
- return null;
364
- }
365
-
366
- const cssModel = this.computedStyleModel.cssModel();
367
- if (!cssModel) {
368
- return null;
369
- }
370
-
371
- return await cssModel.cachedMatchedCascadeForNode(node).then(validateStyles.bind(this));
372
-
373
- function validateStyles(this: ComputedStyleWidget, matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles|null):
374
- SDK.CSSMatchedStyles.CSSMatchedStyles|null {
375
- return matchedStyles && matchedStyles.node() === this.computedStyleModel.node ? matchedStyles : null;
376
- }
377
- }
378
-
379
397
  private async rebuildAlphabeticalList(
380
398
  nodeStyle: ComputedStyleModule.ComputedStyleModel.ComputedStyle,
381
399
  matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles): Promise<void> {
@@ -426,7 +444,7 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
426
444
  this.linkifier.reset();
427
445
  const cssModel = this.computedStyleModel.cssModel();
428
446
  if (!nodeStyle || !matchedStyles || !cssModel) {
429
- this.noMatchesElement.classList.remove('hidden');
447
+ this.#updateView({hasMatches: false});
430
448
  return;
431
449
  }
432
450
 
@@ -648,7 +666,7 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
648
666
  defaultRenderer: this.#treeData.defaultRenderer,
649
667
  compact: this.#treeData.compact,
650
668
  };
651
- this.noMatchesElement.classList.toggle('hidden', Boolean(tree.length));
669
+ this.#updateView({hasMatches: Boolean(tree.length)});
652
670
  }
653
671
 
654
672
  private async filterGroupLists(): Promise<void> {
@@ -675,7 +693,7 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
675
693
  compact: this.#treeData.compact,
676
694
  };
677
695
  await this.#computedStylesTree.expandRecursively(0);
678
- this.noMatchesElement.classList.toggle('hidden', Boolean(tree.length));
696
+ this.#updateView({hasMatches: Boolean(tree.length)});
679
697
  }
680
698
  }
681
699
 
@@ -78,24 +78,37 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
78
78
  };
79
79
 
80
80
  export class PlatformFontsWidget extends UI.Widget.VBox {
81
- private readonly sharedModel: ComputedStyle.ComputedStyleModel.ComputedStyleModel;
82
81
  readonly #view: View;
82
+ #sharedModel: ComputedStyle.ComputedStyleModel.ComputedStyleModel|null = null;
83
83
 
84
- constructor(sharedModel: ComputedStyle.ComputedStyleModel.ComputedStyleModel, view: View = DEFAULT_VIEW) {
85
- super({useShadowDom: true});
84
+ constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
85
+ super(element, {useShadowDom: true});
86
86
  this.#view = view;
87
87
  this.registerRequiredCSS(platformFontsWidgetStyles);
88
+ }
89
+
90
+ get sharedModel(): ComputedStyle.ComputedStyleModel.ComputedStyleModel|null {
91
+ return this.#sharedModel;
92
+ }
93
+
94
+ set sharedModel(model: ComputedStyle.ComputedStyleModel.ComputedStyleModel) {
95
+ if (model !== this.sharedModel) {
96
+ this.sharedModel?.removeEventListener(
97
+ ComputedStyle.ComputedStyleModel.Events.CSS_MODEL_CHANGED, this.requestUpdate, this);
98
+ this.sharedModel?.removeEventListener(
99
+ ComputedStyle.ComputedStyleModel.Events.COMPUTED_STYLE_CHANGED, this.requestUpdate, this);
100
+
101
+ model.addEventListener(ComputedStyle.ComputedStyleModel.Events.CSS_MODEL_CHANGED, this.requestUpdate, this);
102
+ model.addEventListener(ComputedStyle.ComputedStyleModel.Events.COMPUTED_STYLE_CHANGED, this.requestUpdate, this);
103
+ }
88
104
 
89
- this.sharedModel = sharedModel;
90
- this.sharedModel.addEventListener(
91
- ComputedStyle.ComputedStyleModel.Events.CSS_MODEL_CHANGED, this.requestUpdate, this);
92
- this.sharedModel.addEventListener(
93
- ComputedStyle.ComputedStyleModel.Events.COMPUTED_STYLE_CHANGED, this.requestUpdate, this);
105
+ this.#sharedModel = model;
106
+ void this.requestUpdate();
94
107
  }
95
108
 
96
109
  override async performUpdate(): Promise<void> {
97
- const cssModel = this.sharedModel.cssModel();
98
- const node = this.sharedModel.node;
110
+ const cssModel = this.#sharedModel?.cssModel();
111
+ const node = this.#sharedModel?.node;
99
112
  if (!node || !cssModel) {
100
113
  this.#view({platformFonts: null}, {}, this.contentElement);
101
114
  return;
@@ -234,8 +234,49 @@
234
234
  }
235
235
 
236
236
  .green-dev-floaty-disclaimer {
237
- font-size: 14px;
237
+ font-size: 15px;
238
238
  color: #666;
239
239
  text-align: center;
240
240
  margin-top: 8px;
241
+ position: relative; /* For tooltip positioning */
242
+ }
243
+
244
+ .disclaimer-link {
245
+ color: #666; /* Match text color */
246
+ text-decoration: underline;
247
+ cursor: pointer;
248
+ }
249
+
250
+ .disclaimer-tooltip {
251
+ display: none;
252
+ position: absolute;
253
+ bottom: 100%; /* Position above */
254
+ left: 50%;
255
+ transform: translateX(-50%);
256
+ width: 300px;
257
+ padding: 12px;
258
+ background-color: #fff;
259
+ border: 1px solid #ccc;
260
+ border-radius: 8px;
261
+ box-shadow: 0 2px 10px rgb(0 0 0 / 20%);
262
+ z-index: 1000;
263
+ text-align: left;
264
+ font-size: 16px;
265
+ line-height: 1.4;
266
+ color: #333;
267
+ white-space: normal; /* Ensure text wraps */
268
+ }
269
+
270
+ .disclaimer-link:hover + .disclaimer-tooltip,
271
+ .disclaimer-tooltip:hover {
272
+ display: block;
273
+ }
274
+
275
+ .learn-more-link {
276
+ color: #0b57d0;
277
+ text-decoration: none;
278
+ }
279
+
280
+ .learn-more-link:hover {
281
+ text-decoration: underline;
241
282
  }
@@ -4,6 +4,8 @@
4
4
  /* eslint-disable @devtools/no-imperative-dom-api */
5
5
 
6
6
  import type * as Common from '../../core/common/common.js';
7
+ import * as Host from '../../core/host/host.js';
8
+ import type * as Platform from '../../core/platform/platform.js';
7
9
  import * as SDK from '../../core/sdk/sdk.js';
8
10
  import type * as Protocol from '../../generated/protocol.js';
9
11
  import * as UI from '../../ui/legacy/legacy.js';
@@ -186,7 +188,34 @@ export class GreenDevPanel extends UI.Panel.Panel {
186
188
 
187
189
  const disclaimer = document.createElement('div');
188
190
  disclaimer.className = 'green-dev-floaty-disclaimer';
189
- disclaimer.textContent = 'Relevant data is sent to Google';
191
+
192
+ const link = document.createElement('span');
193
+ link.className = 'disclaimer-link';
194
+ link.textContent = 'Relevant data';
195
+ disclaimer.appendChild(link);
196
+
197
+ disclaimer.appendChild(document.createTextNode(' is sent to Google'));
198
+
199
+ const tooltip = document.createElement('div');
200
+ tooltip.className = 'disclaimer-tooltip';
201
+
202
+ tooltip.appendChild(document.createTextNode(
203
+ 'Chat messages and any data the inspected page can access via Web APIs are sent to Google and may be seen by human reviewers to improve this feature. This is an experimental AI feature and won\'t always get it right.'));
204
+ tooltip.appendChild(document.createElement('br'));
205
+ tooltip.appendChild(document.createElement('br'));
206
+
207
+ const learnMore = document.createElement('a');
208
+ const href = 'https://developer.chrome.com/docs/devtools/ai-assistance' as Platform.DevToolsPath.UrlString;
209
+ learnMore.href = href;
210
+ learnMore.className = 'learn-more-link';
211
+ learnMore.textContent = 'Learn about AI in DevTools';
212
+ learnMore.addEventListener('click', event => {
213
+ event.preventDefault();
214
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.openInNewTab(href);
215
+ });
216
+ tooltip.appendChild(learnMore);
217
+
218
+ disclaimer.appendChild(tooltip);
190
219
  blueCard.appendChild(disclaimer);
191
220
 
192
221
  content.appendChild(blueCard);
@@ -184,21 +184,18 @@ export class RequestInitiatorView extends UI.Widget.VBox {
184
184
  if (!initiator?.stack) {
185
185
  return null;
186
186
  }
187
+ const targetManager = SDK.TargetManager.TargetManager.instance();
187
188
  const networkManager = SDK.NetworkManager.NetworkManager.forRequest(request);
188
- const target = networkManager ? networkManager.target() : undefined;
189
+ const target = networkManager?.target() ?? targetManager.primaryPageTarget() ?? targetManager.rootTarget();
190
+ let stackTrace: StackTrace.StackTrace.StackTrace|null = null;
191
+ const preview = new Components.JSPresentationUtils.StackTracePreviewContent(
192
+ undefined, target ?? undefined, linkifier, {tabStops: focusableLink});
189
193
  if (target) {
190
- const stackTrace = await Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance()
191
- .createStackTraceFromProtocolRuntime(initiator.stack, target);
192
- const preview = new Components.JSPresentationUtils.StackTracePreviewContent(
193
- undefined, target, linkifier, {tabStops: focusableLink});
194
+ stackTrace = await Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance()
195
+ .createStackTraceFromProtocolRuntime(initiator.stack, target);
194
196
  preview.stackTrace = stackTrace;
195
- return {preview, stackTrace};
196
197
  }
197
- return {
198
- preview: new Components.JSPresentationUtils.StackTracePreviewContent(
199
- undefined, target, linkifier, {runtimeStackTrace: initiator.stack, tabStops: focusableLink}),
200
- stackTrace: null
201
- };
198
+ return {preview, stackTrace};
202
199
  }
203
200
 
204
201
  override performUpdate(): void {
@@ -576,7 +576,7 @@ export class RequestTimingView extends UI.Widget.VBox {
576
576
  calculator: this.#calculator,
577
577
  requestStartTime: this.#request.startTime,
578
578
  requestIssueTime: this.#request.issueTime(),
579
- requestUnfinished: false,
579
+ requestUnfinished: !this.#request.finished,
580
580
  fetchDetails: this.#fetchDetailsTree(),
581
581
  routerDetails: this.#routerDetailsTree(),
582
582
  wasThrottled: conditions?.urlPattern ? conditions : undefined,
@@ -222,8 +222,9 @@ export class KeybindsSettingsTab extends UI.Widget.VBox implements UI.ListContro
222
222
  return 0;
223
223
  }
224
224
 
225
- isItemSelectable(_item: KeybindsItem): boolean {
226
- return true;
225
+ isItemSelectable(item: KeybindsItem): boolean {
226
+ // Category headers (UI.ActionRegistration.ActionCategory) should not be selectable
227
+ return item instanceof UI.ActionRegistration.Action;
227
228
  }
228
229
 
229
230
  selectedItemChanged(
@@ -314,7 +315,7 @@ export class KeybindsSettingsTab extends UI.Widget.VBox implements UI.ListContro
314
315
  }
315
316
  this.list.refreshAllItems();
316
317
  if (!this.list.selectedItem()) {
317
- this.list.selectItem(this.items.at(0));
318
+ this.list.selectFirstItem();
318
319
  }
319
320
  }
320
321
 
@@ -179,6 +179,25 @@ export function outline(state: CodeMirror.EditorState): OutlineItem[] {
179
179
  ])) {
180
180
  let title = state.sliceDoc(cursor.from, cursor.to);
181
181
  const {lineNumber, columnNumber} = toLineColumn(cursor.from);
182
+ let hasEquals = false;
183
+ let node: CodeMirror.SyntaxNode|null = cursor.node;
184
+ while (node && node.name !== 'AssignmentExpression' && node.name !== 'VariableDeclaration') {
185
+ let sibling = node.nextSibling;
186
+ while (sibling) {
187
+ if (sibling.name === 'Equals') {
188
+ hasEquals = true;
189
+ break;
190
+ }
191
+ sibling = sibling.nextSibling;
192
+ }
193
+ if (hasEquals) {
194
+ break;
195
+ }
196
+ node = node.parent;
197
+ }
198
+ if (!hasEquals) {
199
+ break;
200
+ }
182
201
  while (cursor.name as string !== 'Equals') {
183
202
  if (!cursor.next()) {
184
203
  return items;
@@ -3041,6 +3041,31 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
3041
3041
  this.#setActiveInsight({model: insightModel, insightSetKey}, {highlightInsight: true});
3042
3042
  }
3043
3043
 
3044
+ static async executeRecordAndReload(): Promise<Trace.TraceModel.ParsedTrace> {
3045
+ await UI.ViewManager.ViewManager.instance().showView('timeline');
3046
+ const panelInstance = TimelinePanel.instance();
3047
+
3048
+ const result: EventTypes[Events.RECORDING_COMPLETED] = await new Promise(resolve => {
3049
+ function listener(e: Common.EventTarget.EventTargetEvent<EventTypes[Events.RECORDING_COMPLETED]>): void {
3050
+ resolve(e.data);
3051
+ panelInstance.removeEventListener(Events.RECORDING_COMPLETED, listener);
3052
+ }
3053
+ panelInstance.addEventListener(Events.RECORDING_COMPLETED, listener);
3054
+ panelInstance.recordReload();
3055
+ });
3056
+
3057
+ if ('errorText' in result) {
3058
+ throw new Error(result.errorText);
3059
+ }
3060
+
3061
+ const trace = panelInstance.model.parsedTrace(result.traceIndex);
3062
+ if (!trace) {
3063
+ throw new Error('Failed to parse trace');
3064
+ }
3065
+
3066
+ return trace;
3067
+ }
3068
+
3044
3069
  static async *
3045
3070
  handleExternalRecordRequest(): AsyncGenerator<
3046
3071
  AiAssistanceModel.AiAgent.ExternalRequestResponse, AiAssistanceModel.AiAgent.ExternalRequestResponse> {
@@ -1,7 +1,7 @@
1
1
  Name: Lighthouse
2
2
  Short Name: lighthouse
3
- Version: 13.0.1
4
- Revision: 6ca156ba2950a5220f9f2c8468eee88842eff39a
3
+ Version: 13.0.2
4
+ Revision: 440f8bf87117ef61dc970c3ce4cebf91d46eb181
5
5
  Update Mechanism: Manual
6
6
  URL: https://github.com/GoogleChrome/lighthouse
7
7
  License: Apache-2.0