chrome-devtools-frontend 1.0.1601661 → 1.0.1602543

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 (119) hide show
  1. package/.agents/skills/version-control/SKILL.md +71 -0
  2. package/AUTHORS +1 -0
  3. package/front_end/core/common/Settings.ts +1 -871
  4. package/front_end/core/common/VersionController.ts +880 -0
  5. package/front_end/core/common/common.ts +2 -0
  6. package/front_end/core/host/InspectorFrontendHostStub.ts +0 -5
  7. package/front_end/core/platform/HostRuntime.ts +9 -3
  8. package/front_end/core/protocol_client/InspectorBackend.ts +8 -0
  9. package/front_end/core/root/Runtime.ts +5 -8
  10. package/front_end/core/sdk/DOMModel.ts +5 -4
  11. package/front_end/core/sdk/Target.ts +5 -0
  12. package/front_end/core/sdk/WebMCPModel.ts +98 -0
  13. package/front_end/core/sdk/sdk.ts +2 -0
  14. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +13 -19
  15. package/front_end/entrypoints/inspector_main/InspectorMain.ts +1 -1
  16. package/front_end/entrypoints/main/GlobalAiButton.ts +1 -1
  17. package/front_end/generated/InspectorBackendCommands.ts +6 -5
  18. package/front_end/generated/protocol.ts +36 -3
  19. package/front_end/models/ai_assistance/AiConversation.ts +3 -3
  20. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +217 -9
  21. package/front_end/models/ai_assistance/agents/AiAgent.ts +14 -1
  22. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +19 -4
  23. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +7 -1
  24. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +14 -2
  25. package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -23
  26. package/front_end/models/heap_snapshot_model/HeapSnapshotModel.ts +7 -7
  27. package/front_end/models/javascript_metadata/NativeFunctions.js +4 -0
  28. package/front_end/models/lighthouse/RunTypes.ts +3 -1
  29. package/front_end/models/live-metrics/LiveMetrics.ts +51 -31
  30. package/front_end/panels/accessibility/ARIAAttributesView.ts +2 -2
  31. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +9 -2
  32. package/front_end/panels/ai_assistance/components/ChatInput.ts +3 -2
  33. package/front_end/panels/ai_assistance/components/ChatMessage.ts +84 -27
  34. package/front_end/panels/ai_assistance/components/chatMessage.css +31 -2
  35. package/front_end/panels/application/AppManifestView.ts +2 -2
  36. package/front_end/panels/application/BackgroundServiceView.ts +9 -9
  37. package/front_end/panels/application/FrameDetailsView.ts +4 -13
  38. package/front_end/panels/application/IndexedDBViews.ts +1 -1
  39. package/front_end/panels/application/KeyValueStorageItemsView.ts +4 -4
  40. package/front_end/panels/application/ServiceWorkerCacheViews.ts +3 -3
  41. package/front_end/panels/application/components/PermissionsPolicySection.ts +1 -1
  42. package/front_end/panels/application/components/permissionsPolicySection.css +54 -52
  43. package/front_end/panels/application/frameDetailsReportView.css +13 -1
  44. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +2 -1
  45. package/front_end/panels/console_counters/WarningErrorCounter.ts +7 -14
  46. package/front_end/panels/elements/CSSValueTraceView.ts +9 -1
  47. package/front_end/panels/elements/NodeStackTraceWidget.ts +2 -1
  48. package/front_end/panels/elements/PropertiesWidget.ts +14 -13
  49. package/front_end/panels/elements/PropertyRenderer.ts +10 -9
  50. package/front_end/panels/elements/StandaloneStylesContainer.ts +15 -2
  51. package/front_end/panels/elements/StylePropertyTreeElement.ts +22 -1
  52. package/front_end/panels/elements/StylesSidebarPane.ts +34 -25
  53. package/front_end/panels/elements/stylePropertiesTreeOutline.css +5 -0
  54. package/front_end/panels/issues/AffectedSelectivePermissionsInterventionView.ts +2 -2
  55. package/front_end/panels/lighthouse/LighthouseController.ts +4 -1
  56. package/front_end/panels/media/EventDisplayTable.ts +3 -2
  57. package/front_end/panels/network/EventSourceMessagesView.ts +2 -2
  58. package/front_end/panels/network/RequestPayloadView.ts +2 -2
  59. package/front_end/panels/network/ResourceChunkView.ts +1 -1
  60. package/front_end/panels/profiler/HeapDetachedElementsView.ts +9 -4
  61. package/front_end/panels/profiler/HeapProfileView.ts +8 -4
  62. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +40 -33
  63. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +1 -1
  64. package/front_end/panels/profiler/HeapSnapshotProxy.ts +1 -1
  65. package/front_end/panels/profiler/HeapSnapshotView.ts +12 -18
  66. package/front_end/panels/profiler/LiveHeapProfileView.ts +3 -4
  67. package/front_end/panels/profiler/ProfileHeader.ts +5 -11
  68. package/front_end/panels/profiler/ProfileView.ts +1 -1
  69. package/front_end/panels/recorder/RecorderController.ts +4 -3
  70. package/front_end/panels/recorder/components/CreateRecordingView.ts +2 -1
  71. package/front_end/panels/recorder/components/RecordingView.ts +2 -2
  72. package/front_end/panels/recorder/models/RecordingPlayer.ts +3 -0
  73. package/front_end/panels/security/SecurityPanel.ts +3 -3
  74. package/front_end/panels/sensors/SensorsView.ts +149 -125
  75. package/front_end/panels/timeline/EventsTimelineTreeView.ts +2 -2
  76. package/front_end/panels/timeline/TimelineDetailsView.ts +6 -37
  77. package/front_end/panels/timeline/TimelineTreeView.ts +19 -17
  78. package/front_end/panels/timeline/TimelineUIUtils.ts +0 -118
  79. package/front_end/panels/timeline/components/IgnoreListSetting.ts +1 -1
  80. package/front_end/panels/timeline/components/SidebarInsightsTab.ts +2 -2
  81. package/front_end/panels/timeline/components/TimelineRangeSummaryView.ts +162 -9
  82. package/front_end/panels/timeline/components/timelineRangeSummaryView.css +22 -8
  83. package/front_end/panels/webauthn/WebauthnPane.ts +52 -0
  84. package/front_end/panels/whats_new/ReleaseNoteText.ts +10 -10
  85. package/front_end/panels/whats_new/resources/WNDT.md +6 -6
  86. package/front_end/third_party/chromium/README.chromium +1 -1
  87. package/front_end/third_party/puppeteer/README.chromium +2 -2
  88. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +4 -0
  90. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
  91. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +2 -2
  92. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +2 -2
  93. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  96. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +3 -3
  97. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  98. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +4 -0
  99. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
  100. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +2 -2
  101. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +2 -2
  102. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  104. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  105. package/front_end/third_party/puppeteer/package/package.json +1 -1
  106. package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +8 -0
  107. package/front_end/third_party/puppeteer/package/src/revisions.ts +2 -2
  108. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  109. package/front_end/ui/legacy/Toolbar.ts +25 -3
  110. package/front_end/ui/legacy/Treeoutline.ts +7 -3
  111. package/front_end/ui/legacy/UIUtils.ts +13 -11
  112. package/front_end/ui/legacy/Widget.ts +40 -40
  113. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +1 -1
  114. package/front_end/ui/legacy/components/data_grid/SortableDataGrid.ts +2 -2
  115. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +39 -23
  116. package/front_end/ui/legacy/components/perf_ui/GCActionDelegate.ts +20 -4
  117. package/front_end/ui/legacy/treeoutline.css +4 -0
  118. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  119. package/package.json +1 -1
@@ -54,7 +54,7 @@ export interface AiConversationOptions {
54
54
  performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>;
55
55
  onInspectElement?: () => Promise<SDK.DOMModel.DOMNode|null>;
56
56
  networkTimeCalculator?: NetworkTimeCalculator.NetworkTransferTimeCalculator;
57
- lighthouseRecording?: () => Promise<LHModel.ReporterTypes.ReportJSON|null>;
57
+ lighthouseRecording?: (overrides?: LHModel.RunTypes.RunOverrides) => Promise<LHModel.ReporterTypes.ReportJSON|null>;
58
58
  }
59
59
 
60
60
  export class AiConversation {
@@ -91,7 +91,7 @@ export class AiConversation {
91
91
  #contexts: Array<ConversationContext<unknown>> = [];
92
92
 
93
93
  #performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>;
94
- #lighthouseRecording?: () => Promise<LHModel.ReporterTypes.ReportJSON|null>;
94
+ #lighthouseRecording?: (overrides?: LHModel.RunTypes.RunOverrides) => Promise<LHModel.ReporterTypes.ReportJSON|null>;
95
95
  #onInspectElement?: () => Promise<SDK.DOMModel.DOMNode|null>;
96
96
  #networkTimeCalculator?: NetworkTimeCalculator.NetworkTransferTimeCalculator;
97
97
 
@@ -334,9 +334,9 @@ export class AiConversation {
334
334
  sessionId: this.id,
335
335
  changeManager: this.#changeManager,
336
336
  performanceRecordAndReload: this.#performanceRecordAndReload,
337
- lighthouseRecording: this.#lighthouseRecording,
338
337
  onInspectElement: this.#onInspectElement,
339
338
  networkTimeCalculator: this.#networkTimeCalculator,
339
+ lighthouseRecording: this.#lighthouseRecording,
340
340
  allowedOrigin: this.allowedOrigin,
341
341
  history,
342
342
  };
@@ -5,11 +5,13 @@
5
5
  import * as Host from '../../../core/host/host.js';
6
6
  import * as i18n from '../../../core/i18n/i18n.js';
7
7
  import * as Root from '../../../core/root/root.js';
8
+ import * as SDK from '../../../core/sdk/sdk.js';
8
9
  import type * as LHModel from '../../lighthouse/lighthouse.js';
9
10
  import {LighthouseFormatter} from '../data_formatters/LighthouseFormatter.js';
10
11
  import {debugLog} from '../debug.js';
11
12
 
12
13
  import {
14
+ type AgentOptions,
13
15
  AiAgent,
14
16
  type ContextDetail,
15
17
  type ContextResponse,
@@ -23,20 +25,30 @@ import {
23
25
  * TESTERS. Otherwise, a server-side preamble is used (see
24
26
  * chrome_preambles.gcl). Sync local changes with the server-side.
25
27
  */
26
- const preamble = `You are an accessibility expert agent.
28
+ const preamble = `You are an accessibility expert agent integrated into Chrome DevTools.
29
+ Your role is to help users understand and fix accessibility issues found in Lighthouse reports.
27
30
 
28
- # Goals
29
- * Help users understand and fix accessibility issues found in Lighthouse reports.
30
- * Provide succinct, actionable advice. Avoid long explanations and "walls of text".
31
- * Focus on the most critical information first, prioritizing audits with low scores.
31
+ # Style Guidelines
32
+ * **Concise and Direct**: Use short sentences and bullet points. Avoid paragraphs and long explanations.
33
+ * **Structured**: Organize your findings by problem, root cause, and next steps, but do NOT use those literal words as headings.
34
+ * **No Internal Identifiers**: NEVER show Lighthouse paths (e.g., "1,HTML,1,BODY...") to the user. Refer to elements by their tag name, classes, or IDs.
35
+ * **Managing Volume**: If the report contains many issues, provide a brief summary of the top 2-3 most critical ones. Tell the user that there are more issues and invite them to ask for more details or to explore a specific area.
36
+
37
+ # Workflow
38
+ 1. **Identify**: Find the most critical accessibility issues in the Lighthouse report.
39
+ 2. **Investigate**: For any element identified as failing, you **MUST** call \`getStyles\` or \`getElementAccessibilityDetails\` first to confirm its current state and gather details.
40
+ 3. **Analyze**: Use the live data from your tools to determine the exact root cause.
41
+ 4. **Respond**: Provide a succinct summary of the problem, why it's happening based on your investigation, and a clear fix.
32
42
 
33
43
  # Capabilities
34
- * You have access to the \`getLighthouseAudits\` function to retrieve detailed audit data for performance, accessibility, best-practices, and SEO.
35
- * Proactively use this function to investigate categories with low scores and help the user focus on the most important areas.
44
+ * \`getLighthouseAudits\`: Get detailed audit data.
45
+ * \`runAccessibilityAudits\`: Trigger new accessibility snapshot audits.
46
+ * \`getStyles\`: Get computed styles for an element by its path.
47
+ * \`getElementAccessibilityDetails\`: Get A11y properties for an element by its path.
36
48
 
37
49
  # Constraints
38
- * Keep your analysis concise and focused, highlighting only the most critical aspects for a software engineer.
39
- * **CRITICAL** You are an accessibility agent. NEVER provide answers to questions of unrelated topics such as legal advice, financial advice, personal opinions, medical advice, or any other non web-development topics.
50
+ * **CRITICAL**: ALWAYS call a tool before providing an answer if an element path is available.
51
+ * **CRITICAL**: You are an accessibility agent. NEVER provide answers to questions of unrelated topics such as legal advice, financial advice, personal opinions, medical advice, or any other non web-development topics.
40
52
  `;
41
53
 
42
54
  export class AccessibilityContext extends ConversationContext<LHModel.ReporterTypes.ReportJSON> {
@@ -71,6 +83,13 @@ export class AccessibilityContext extends ConversationContext<LHModel.ReporterTy
71
83
  export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON> {
72
84
  readonly preamble = preamble;
73
85
  readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_ACCESSIBILITY_AGENT;
86
+ readonly #lighthouseRecording?:
87
+ (overrides?: LHModel.RunTypes.RunOverrides) => Promise<LHModel.ReporterTypes.ReportJSON|null>;
88
+
89
+ constructor(opts: AgentOptions) {
90
+ super(opts);
91
+ this.#lighthouseRecording = opts.lighthouseRecording;
92
+ }
74
93
 
75
94
  get userTier(): string|undefined {
76
95
  return Root.Runtime.hostConfig.devToolsFreestyler?.userTier;
@@ -100,7 +119,64 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
100
119
  };
101
120
  }
102
121
 
122
+ async #resolvePathToNode(path: string): Promise<SDK.DOMModel.DOMNode|null> {
123
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
124
+ if (!target) {
125
+ return null;
126
+ }
127
+ const domModel = target.model(SDK.DOMModel.DOMModel);
128
+ if (!domModel) {
129
+ return null;
130
+ }
131
+ const nodeId = await domModel.pushNodeByPathToFrontend(path);
132
+ if (!nodeId) {
133
+ return null;
134
+ }
135
+ return domModel.nodeForId(nodeId);
136
+ }
137
+
103
138
  #declareFunctions(): void {
139
+ this.declareFunction<{explanation: string}, {audits: string}>('runAccessibilityAudits', {
140
+ description:
141
+ 'Triggers new Lighthouse accessibility audits in snapshot mode. Use this if the user has made changes to the page and you want to re-evaluate the accessibility audits.',
142
+ parameters: {
143
+ type: Host.AidaClient.ParametersTypes.OBJECT,
144
+ description: '',
145
+ nullable: false,
146
+ properties: {
147
+ explanation: {
148
+ type: Host.AidaClient.ParametersTypes.STRING,
149
+ description: 'Explain why you want to run new audits.',
150
+ nullable: false,
151
+ },
152
+ },
153
+ required: ['explanation'],
154
+ },
155
+ displayInfoFromArgs: params => {
156
+ return {
157
+ title: i18n.i18n.lockedString('Running accessibility audits…'),
158
+ thought: params.explanation,
159
+ action: 'runAccessibilityAudits()'
160
+ };
161
+ },
162
+ handler: async params => {
163
+ debugLog('Function call: runAccessibilityAudits', params);
164
+ if (!this.#lighthouseRecording) {
165
+ return {error: 'Lighthouse recording is not available.'};
166
+ }
167
+ const report = await this.#lighthouseRecording({
168
+ mode: 'snapshot',
169
+ categoryIds: ['accessibility'],
170
+ isAIControlled: true,
171
+ });
172
+ if (!report) {
173
+ return {error: 'Failed to run accessibility audits.'};
174
+ }
175
+ const audits = new LighthouseFormatter().audits(report, 'accessibility');
176
+ return {result: {audits}};
177
+ }
178
+ });
179
+
104
180
  this.declareFunction<{categoryId: LHModel.RunTypes.CategoryId}, {audits: string}>('getLighthouseAudits', {
105
181
  description:
106
182
  'Returns the audits for a specific Lighthouse category. Use this to get more information about the performance, accessibility, best-practices, or seo audits.',
@@ -134,6 +210,138 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
134
210
  return {result: {audits}};
135
211
  }
136
212
  });
213
+
214
+ this.declareFunction<{
215
+ path: string,
216
+ styleProperties: string[],
217
+ explanation: string,
218
+ }>('getStyles', {
219
+ description: 'Get computed styles for an element on the inspected page by its Lighthouse path.',
220
+ parameters: {
221
+ type: Host.AidaClient.ParametersTypes.OBJECT,
222
+ description: '',
223
+ nullable: false,
224
+ properties: {
225
+ explanation: {
226
+ type: Host.AidaClient.ParametersTypes.STRING,
227
+ description: 'Explain why you want to get styles.',
228
+ nullable: false,
229
+ },
230
+ path: {
231
+ type: Host.AidaClient.ParametersTypes.STRING,
232
+ description:
233
+ 'The Lighthouse path of the element (e.g., "1,HTML,1,BODY,2,DIV"). Find this in the report data.',
234
+ nullable: false,
235
+ },
236
+ styleProperties: {
237
+ type: Host.AidaClient.ParametersTypes.ARRAY,
238
+ description: 'One or more CSS style property names to fetch.',
239
+ nullable: false,
240
+ items: {
241
+ type: Host.AidaClient.ParametersTypes.STRING,
242
+ description: 'A CSS style property name to retrieve. For example, \'background-color\'.'
243
+ }
244
+ },
245
+ },
246
+ required: ['explanation', 'path', 'styleProperties']
247
+ },
248
+ displayInfoFromArgs: params => {
249
+ return {
250
+ title: 'Reading computed styles',
251
+ thought: params.explanation,
252
+ action: `getStyles('${params.path}', ${JSON.stringify(params.styleProperties)})`,
253
+ };
254
+ },
255
+ handler: async params => {
256
+ debugLog('Function call: getStyles', params);
257
+ const node = await this.#resolvePathToNode(params.path);
258
+ if (!node) {
259
+ return {error: `Could not find the element with path: ${params.path}`};
260
+ }
261
+ const styles = await node.domModel().cssModel().getComputedStyle(node.id);
262
+ if (!styles) {
263
+ return {error: 'Could not get computed styles.'};
264
+ }
265
+ const result: Record<string, string|undefined> = {};
266
+ for (const prop of params.styleProperties) {
267
+ result[prop] = styles.get(prop);
268
+ }
269
+ return {result: JSON.stringify(result, null, 2)};
270
+ },
271
+ });
272
+
273
+ this.declareFunction<{
274
+ path: string,
275
+ explanation: string,
276
+ }>('getElementAccessibilityDetails', {
277
+ description:
278
+ 'Get detailed accessibility information for an element on the inspected page by its Lighthouse path.',
279
+ parameters: {
280
+ type: Host.AidaClient.ParametersTypes.OBJECT,
281
+ description: '',
282
+ nullable: false,
283
+ properties: {
284
+ explanation: {
285
+ type: Host.AidaClient.ParametersTypes.STRING,
286
+ description: 'Explain why you want to get accessibility details.',
287
+ nullable: false,
288
+ },
289
+ path: {
290
+ type: Host.AidaClient.ParametersTypes.STRING,
291
+ description:
292
+ 'The Lighthouse path of the element (e.g., "1,HTML,1,BODY,2,DIV"). Find this in the report data.',
293
+ nullable: false,
294
+ },
295
+ },
296
+ required: ['explanation', 'path']
297
+ },
298
+ displayInfoFromArgs: params => {
299
+ return {
300
+ title: 'Reading accessibility details',
301
+ thought: params.explanation,
302
+ action: `getElementAccessibilityDetails('${params.path}')`,
303
+ };
304
+ },
305
+ handler: async params => {
306
+ debugLog('Function call: getElementAccessibilityDetails', params);
307
+ const node = await this.#resolvePathToNode(params.path);
308
+ if (!node) {
309
+ return {error: `Could not find the element with path: ${params.path}`};
310
+ }
311
+ const accessibilityModel = node.domModel().target().model(SDK.AccessibilityModel.AccessibilityModel);
312
+ if (!accessibilityModel) {
313
+ return {error: 'Accessibility model not found.'};
314
+ }
315
+ await accessibilityModel.requestAndLoadSubTreeToNode(node);
316
+ const axNode = accessibilityModel.axNodeForDOMNode(node);
317
+ if (!axNode) {
318
+ return {error: 'Could not find accessibility node for the element.'};
319
+ }
320
+
321
+ const result = {
322
+ role: axNode.role()?.value,
323
+ name: axNode.name()?.value,
324
+ nameSource: axNode.name()?.sources?.[0]?.type,
325
+ properties: {
326
+ focusable: node.getAttribute('tabindex') !== undefined || axNode.role()?.value === 'button' ||
327
+ axNode.role()?.value === 'link',
328
+ hidden: axNode.ignored(),
329
+ },
330
+ ariaAttributes: node.attributes()
331
+ .filter(attr => attr.name.startsWith('aria-') || attr.name === 'role')
332
+ .reduce(
333
+ (acc, attr) => {
334
+ acc[attr.name] = attr.value;
335
+ return acc;
336
+ },
337
+ {} as Record<string, string>),
338
+ isIgnored: axNode.ignored(),
339
+ ignoredReasons: axNode.ignoredReasons(),
340
+ };
341
+
342
+ return {result: JSON.stringify(result, null, 2)};
343
+ },
344
+ });
137
345
  }
138
346
 
139
347
  /**
@@ -7,6 +7,7 @@ import * as Root from '../../../core/root/root.js';
7
7
  import type * as SDK from '../../../core/sdk/sdk.js';
8
8
  import type * as Protocol from '../../../generated/protocol.js';
9
9
  import * as Greendev from '../../greendev/greendev.js';
10
+ import type * as LHModel from '../../lighthouse/lighthouse.js';
10
11
  import type * as Trace from '../../trace/trace.js';
11
12
  import {debugLog, isStructuredLogEnabled} from '../debug.js';
12
13
 
@@ -151,6 +152,7 @@ export interface AgentOptions {
151
152
  onInspectElement?: () => Promise<SDK.DOMModel.DOMNode|null>;
152
153
  history?: Host.AidaClient.Content[];
153
154
  allowedOrigin?: () => string | undefined;
155
+ lighthouseRecording?: (overrides?: LHModel.RunTypes.RunOverrides) => Promise<LHModel.ReporterTypes.ReportJSON|null>;
154
156
  }
155
157
 
156
158
  export interface ParsedAnswer {
@@ -277,9 +279,20 @@ export interface LcpBreakdownAiWidget {
277
279
  };
278
280
  }
279
281
 
282
+ export interface TimelineRangeSummaryAiWidget {
283
+ name: 'TIMELINE_RANGE_SUMMARY';
284
+ data: {
285
+ bounds: Trace.Types.Timing.TraceWindowMicro,
286
+ parsedTrace: Trace.TraceModel.ParsedTrace,
287
+ // We can use this component for other tracks summaries later, so
288
+ // we include the track in the data.
289
+ track: 'main',
290
+ };
291
+ }
292
+
280
293
  // This type will grow as we add more widgets.
281
294
  export type AiWidget = ComputedStyleAiWidget|CoreVitalsAiWidget|StylePropertiesAiWidget|DomTreeAiWidget|
282
- PerformanceTraceAiWidget|LcpBreakdownAiWidget;
295
+ PerformanceTraceAiWidget|LcpBreakdownAiWidget|TimelineRangeSummaryAiWidget;
283
296
 
284
297
  export type FunctionCallHandlerResult<Result> = {
285
298
  requiresApproval: true,
@@ -82,14 +82,14 @@ export class ContextSelectionAgent extends AiAgent<never> {
82
82
  readonly #performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>;
83
83
  readonly #onInspectElement?: () => Promise<SDK.DOMModel.DOMNode|null>;
84
84
  readonly #networkTimeCalculator?: NetworkTimeCalculator.NetworkTransferTimeCalculator;
85
- readonly #lighthouseRecording?: (() => Promise<LHModel.ReporterTypes.ReportJSON|null>);
85
+ readonly #lighthouseRecording?:
86
+ (overrides?: LHModel.RunTypes.RunOverrides) => Promise<LHModel.ReporterTypes.ReportJSON|null>;
86
87
  #allowedOrigin: () => string | undefined;
87
88
 
88
89
  constructor(opts: AgentOptions&{
89
90
  performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>,
90
91
  onInspectElement?: () => Promise<SDK.DOMModel.DOMNode|null>,
91
92
  networkTimeCalculator?: NetworkTimeCalculator.NetworkTransferTimeCalculator,
92
- lighthouseRecording?: () => Promise<LHModel.ReporterTypes.ReportJSON|null>,
93
93
  }) {
94
94
  super(opts);
95
95
  this.#performanceRecordAndReload = opts.performanceRecordAndReload;
@@ -119,7 +119,16 @@ export class ContextSelectionAgent extends AiAgent<never> {
119
119
 
120
120
  let hasCrossOriginRequest = false;
121
121
  for (const request of Logs.NetworkLog.NetworkLog.instance().requests()) {
122
- if (origin && request.securityOrigin() !== origin) {
122
+ const requestOrigin = new URL(request.documentURL).origin;
123
+ /**
124
+ * NOTE: this origin check does not ensure that all the requests are
125
+ * from the same origin as the target page. Instead, it ensures that
126
+ * the document that loaded the request is the same as the target
127
+ * page. This ensures that we limit the scope to all requests fetched
128
+ * during the loading of the target page, and do not leak URLs from
129
+ * other pages.
130
+ */
131
+ if (origin && requestOrigin !== origin) {
123
132
  hasCrossOriginRequest = true;
124
133
  continue;
125
134
  }
@@ -170,8 +179,14 @@ export class ContextSelectionAgent extends AiAgent<never> {
170
179
  };
171
180
  },
172
181
  handler: async ({id}) => {
182
+ const origin = this.#allowedOrigin();
173
183
  const request = Logs.NetworkLog.NetworkLog.instance().requests().find(req => {
174
- return req.requestId() === id;
184
+ if (req.requestId() !== id) {
185
+ return false;
186
+ }
187
+
188
+ const requestOrigin = new URL(req.documentURL).origin;
189
+ return !origin || requestOrigin === origin;
175
190
  });
176
191
 
177
192
  if (request) {
@@ -95,8 +95,14 @@ export class RequestContext extends ConversationContext<SDK.NetworkRequest.Netwo
95
95
  this.#calculator = calculator;
96
96
  }
97
97
 
98
+ /**
99
+ * Note: this is not the literal origin of the network request. This origin
100
+ * is used to determine when we should force the user to start a new AI
101
+ * conversation when the context changes. We allow a single AI conversation to
102
+ * inspect all network requests that were made for that given target URL.
103
+ */
98
104
  override getOrigin(): string {
99
- return new URL(this.#request.url()).origin;
105
+ return this.#request.documentURL;
100
106
  }
101
107
 
102
108
  override getItem(): SDK.NetworkRequest.NetworkRequest {
@@ -942,7 +942,17 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
942
942
 
943
943
  const key = `getMainThreadTrackSummary({min: ${bounds.min}, max: ${bounds.max}})`;
944
944
  this.#cacheFunctionResult(focus, key, summary);
945
- return {result: {summary}};
945
+ return {
946
+ result: {summary},
947
+ widgets: [{
948
+ name: 'TIMELINE_RANGE_SUMMARY',
949
+ data: {
950
+ parsedTrace,
951
+ bounds,
952
+ track: 'main',
953
+ },
954
+ }],
955
+ };
946
956
  },
947
957
 
948
958
  });
@@ -999,7 +1009,9 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
999
1009
 
1000
1010
  const key = `getNetworkTrackSummary({min: ${bounds.min}, max: ${bounds.max}})`;
1001
1011
  this.#cacheFunctionResult(focus, key, summary);
1002
- return {result: {summary}};
1012
+ return {
1013
+ result: {summary},
1014
+ };
1003
1015
  },
1004
1016
 
1005
1017
  });
@@ -20,7 +20,6 @@ import {AI_ASSISTANCE_CSS_CLASS_NAME, FREESTYLER_WORLD_NAME} from '../injected.j
20
20
  import {
21
21
  type AgentOptions as BaseAgentOptions,
22
22
  AiAgent,
23
- type AnswerResponse,
24
23
  type ComputedStyleAiWidget,
25
24
  type ContextResponse,
26
25
  ConversationContext,
@@ -1052,28 +1051,6 @@ const data = {
1052
1051
  this.#currentTurnId++;
1053
1052
  }
1054
1053
 
1055
- protected override async finalizeAnswer(answer: AnswerResponse): Promise<AnswerResponse> {
1056
- if (!Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled) {
1057
- return answer;
1058
- }
1059
-
1060
- const changedNodeIds = this.#changes.getChangedNodesForGroupId(this.sessionId, this.#currentTurnId);
1061
- if (changedNodeIds.length === 0) {
1062
- return answer;
1063
- }
1064
- answer.widgets = [
1065
- ...(answer.widgets ?? []),
1066
- ...changedNodeIds.map(id => ({
1067
- name: 'STYLE_PROPERTIES' as const,
1068
- data: {
1069
- backendNodeId: id,
1070
- selector: AI_ASSISTANCE_FILTER_REGEX,
1071
- },
1072
- })),
1073
- ];
1074
- return answer;
1075
- }
1076
-
1077
1054
  override async enhanceQuery(
1078
1055
  query: string, selectedElement: ConversationContext<SDK.DOMModel.DOMNode>|null,
1079
1056
  multimodalInputType?: MultimodalInputType): Promise<string> {
@@ -108,13 +108,13 @@ export class Edge {
108
108
  }
109
109
  }
110
110
 
111
- export class Aggregate {
112
- count!: number;
113
- distance!: number;
114
- self!: number;
115
- maxRet!: number;
116
- name!: string;
117
- idxs!: number[];
111
+ export interface AggregatedInfo {
112
+ count: number;
113
+ distance: number;
114
+ self: number;
115
+ maxRet: number;
116
+ name: string;
117
+ idxs: number[];
118
118
  }
119
119
 
120
120
  export class AggregateForDiff {
@@ -6904,6 +6904,10 @@ export const NativeFunctions = [
6904
6904
  name: "MouseEvent",
6905
6905
  signatures: [["type","?eventInitDict"]]
6906
6906
  },
6907
+ {
6908
+ name: "PageHideEvent",
6909
+ signatures: [["type","?eventInitDict"]]
6910
+ },
6907
6911
  {
6908
6912
  name: "PageTransitionEvent",
6909
6913
  signatures: [["type","?eventInitDict"]]
@@ -6,6 +6,7 @@ import type * as Host from '../../core/host/host.js';
6
6
  import type * as Platform from '../../core/platform/platform.js';
7
7
 
8
8
  export type CategoryId = 'performance'|'accessibility'|'best-practices'|'seo';
9
+ export type RunMode = 'navigation'|'timespan'|'snapshot';
9
10
 
10
11
  export interface Preset {
11
12
  setting: Common.Settings.Setting<boolean>;
@@ -33,10 +34,11 @@ export interface Flags {
33
34
  disableStorageReset?: boolean;
34
35
  throttlingMethod?: string;
35
36
  formFactor?: string|boolean;
36
- mode?: string|boolean;
37
+ mode?: RunMode;
37
38
  }
38
39
 
39
40
  export interface RunOverrides {
40
41
  categoryIds?: CategoryId[];
42
+ mode?: RunMode;
41
43
  isAIControlled?: boolean;
42
44
  }
@@ -62,29 +62,7 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
62
62
 
63
63
  private constructor() {
64
64
  super();
65
- const targetManager = SDK.TargetManager.TargetManager.instance();
66
- targetManager.observeTargets(this, {scoped: true});
67
- // Listen for target info changes to detect prerender activation.
68
- // Scoped observers don't receive events when a prerendered target becomes
69
- // primary because setScopeTarget() isn't called during that transition.
70
- targetManager.addEventListener(
71
- SDK.TargetManager.Events.AVAILABLE_TARGETS_CHANGED, this.#onAvailableTargetsChanged, this);
72
- }
73
-
74
- #onAvailableTargetsChanged(): void {
75
- const primaryTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
76
- if (primaryTarget && primaryTarget !== this.#target) {
77
- // Primary target changed (e.g., prerender activation). Switch to it.
78
- void this.#switchToTarget(primaryTarget);
79
- }
80
- }
81
-
82
- async #switchToTarget(newTarget: SDK.Target.Target): Promise<void> {
83
- if (this.#target) {
84
- await this.disable();
85
- }
86
- this.#target = newTarget;
87
- await this.enable();
65
+ SDK.TargetManager.TargetManager.instance().observeTargets(this);
88
66
  }
89
67
 
90
68
  static instance(opts: {forceNew?: boolean} = {forceNew: false}): LiveMetrics {
@@ -393,6 +371,31 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
393
371
  this.#sendStatusUpdate();
394
372
  }
395
373
 
374
+ async #getFrameForExecutionContextId(executionContextId: Protocol.Runtime.ExecutionContextId):
375
+ Promise<SDK.ResourceTreeModel.ResourceTreeFrame|null> {
376
+ if (!this.#target) {
377
+ return null;
378
+ }
379
+
380
+ const runtimeModel = this.#target.model(SDK.RuntimeModel.RuntimeModel);
381
+ if (!runtimeModel) {
382
+ return null;
383
+ }
384
+
385
+ const executionContext = runtimeModel.executionContext(executionContextId);
386
+ if (!executionContext) {
387
+ return null;
388
+ }
389
+
390
+ const frameId = executionContext.frameId;
391
+ if (!frameId) {
392
+ return null;
393
+ }
394
+
395
+ const frameManager = SDK.FrameManager.FrameManager.instance();
396
+ return await frameManager.getOrWaitForFrame(frameId);
397
+ }
398
+
396
399
  async #onBindingCalled(event: {data: Protocol.Runtime.BindingCalledEvent}): Promise<void> {
397
400
  const {data} = event;
398
401
  if (data.name !== Spec.EVENT_BINDING_NAME) {
@@ -404,8 +407,22 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
404
407
  await this.#mutex.run(async () => {
405
408
  const webVitalsEvent = JSON.parse(data.payload) as Spec.WebVitalsEvent;
406
409
 
407
- // Track the execution context from 'reset' events for logInteractionScripts().
408
- if (webVitalsEvent.name === 'reset') {
410
+ // This ensures that `#lastResetContextId` will always be an execution context on the
411
+ // primary frame. If we receive events from this execution context then we automatically
412
+ // know that they are for the primary frame.
413
+ if (this.#lastResetContextId !== data.executionContextId) {
414
+ if (webVitalsEvent.name !== 'reset') {
415
+ return;
416
+ }
417
+
418
+ // We should avoid calling this function for every event.
419
+ // If an interaction triggers a pre-rendered navigation then the old primary frame could
420
+ // be removed before we reach this point, and then it will hang forever.
421
+ const frame = await this.#getFrameForExecutionContextId(data.executionContextId);
422
+ if (!frame?.isPrimaryFrame()) {
423
+ return;
424
+ }
425
+
409
426
  this.#lastResetContextId = data.executionContextId;
410
427
  }
411
428
 
@@ -447,7 +464,6 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
447
464
  }
448
465
 
449
466
  async targetAdded(target: SDK.Target.Target): Promise<void> {
450
- // Scoped observers can also receive events for OOPIFs and workers.
451
467
  if (target !== SDK.TargetManager.TargetManager.instance().primaryPageTarget()) {
452
468
  return;
453
469
  }
@@ -456,12 +472,20 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
456
472
  }
457
473
 
458
474
  async targetRemoved(target: SDK.Target.Target): Promise<void> {
459
- // Scoped observers can also receive events for OOPIFs and workers.
460
475
  if (target !== this.#target) {
461
476
  return;
462
477
  }
463
478
  await this.disable();
464
479
  this.#target = undefined;
480
+
481
+ // If the user navigates to a page that was pre-rendered then the primary page target
482
+ // will be swapped and the old target will be removed. We should ensure live metrics
483
+ // remain enabled on the new primary page target.
484
+ const primaryPageTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
485
+ if (primaryPageTarget) {
486
+ this.#target = primaryPageTarget;
487
+ await this.enable();
488
+ }
465
489
  }
466
490
 
467
491
  async enable(): Promise<void> {
@@ -531,10 +555,6 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
531
555
  return;
532
556
  }
533
557
 
534
- // Reset to ensure clean state when re-enabling on a new target.
535
- // See crbug.com/478832430.
536
- this.#lastResetContextId = undefined;
537
-
538
558
  await this.#killAllLiveMetricContexts();
539
559
 
540
560
  const runtimeModel = this.#target.model(SDK.RuntimeModel.RuntimeModel);
@@ -26,6 +26,7 @@ const UIStrings = {
26
26
  const str_ = i18n.i18n.registerUIStrings('panels/accessibility/ARIAAttributesView.ts', UIStrings);
27
27
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
28
28
  const {render, html} = Lit;
29
+ const {widget} = UI.Widget;
29
30
 
30
31
  interface ViewInput {
31
32
  propertyCompletions: Map<SDK.DOMModel.Attribute, string[]>;
@@ -62,8 +63,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
62
63
  html`
63
64
  <style>${accessibilityPropertiesStyles}</style>
64
65
  <devtools-widget
65
- .widgetConfig=${UI.Widget.widgetConfig(UI.EmptyWidget.EmptyWidget,
66
- {text: i18nString(UIStrings.noAriaAttributes)})}
66
+ ${widget(UI.EmptyWidget.EmptyWidget, {text: i18nString(UIStrings.noAriaAttributes)})}
67
67
  class="gray-info-message info-message-overflow"></devtools-widget>` :
68
68
  html`<devtools-tree
69
69
  hide-overflow