chrome-devtools-frontend 1.0.1651641 → 1.0.1654411

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 (96) hide show
  1. package/.agents/skills/fixing-skipped-tests/SKILL.md +32 -0
  2. package/.agents/skills/migrate-chromium-test/SKILL.md +144 -0
  3. package/docs/get_the_code.md +1 -2
  4. package/docs/ui_engineering.md +3 -1
  5. package/eslint.config.mjs +0 -5
  6. package/front_end/core/sdk/CPUThrottlingManager.ts +13 -8
  7. package/front_end/core/sdk/CSSMetadata.ts +23 -0
  8. package/front_end/core/sdk/CompilerSourceMappingContentProvider.ts +8 -4
  9. package/front_end/core/sdk/DOMModel.ts +4 -0
  10. package/front_end/core/sdk/EmulationModel.ts +4 -0
  11. package/front_end/core/sdk/ResourceTreeModel.ts +3 -2
  12. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +459 -74
  13. package/front_end/entrypoints/main/MainImpl.ts +0 -6
  14. package/front_end/foundation/Universe.ts +18 -0
  15. package/front_end/generated/InspectorBackendCommands.ts +7 -6
  16. package/front_end/generated/SupportedCSSProperties.js +208 -34
  17. package/front_end/generated/protocol.ts +64 -7
  18. package/front_end/models/ai_assistance/AiAgent2.ts +3 -3
  19. package/front_end/models/ai_assistance/AiConversation.ts +2 -1
  20. package/front_end/models/ai_assistance/README.md +5 -4
  21. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +1 -1
  22. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +2 -2
  23. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +11 -164
  24. package/front_end/models/ai_assistance/ai_assistance.ts +6 -2
  25. package/front_end/models/ai_assistance/contexts/PerformanceTraceContext.ts +295 -0
  26. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +10 -10
  27. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +12 -11
  28. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +6 -6
  29. package/front_end/models/ai_assistance/skills/accessibility.md +4 -2
  30. package/front_end/models/ai_assistance/tools/GetElementAccessibilityDetails.ts +142 -0
  31. package/front_end/models/ai_assistance/tools/GetStyles.ts +1 -1
  32. package/front_end/models/ai_assistance/tools/README.md +22 -2
  33. package/front_end/models/ai_assistance/tools/{ResolveLighthousePath.ts → ResolveDevtoolsNodePath.ts} +18 -17
  34. package/front_end/models/ai_assistance/tools/Tool.ts +7 -2
  35. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +10 -8
  36. package/front_end/models/autofill_manager/AutofillManager.ts +7 -15
  37. package/front_end/models/bindings/NetworkProject.ts +4 -8
  38. package/front_end/models/breakpoints/BreakpointManager.ts +9 -9
  39. package/front_end/models/emulation/DeviceModeModel.ts +24 -0
  40. package/front_end/models/emulation/EmulatedDevices.ts +108 -0
  41. package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +36 -0
  42. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +8 -0
  43. package/front_end/models/issues_manager/IssuesManager.ts +10 -6
  44. package/front_end/models/javascript_metadata/JavaScriptMetadata.ts +4 -5
  45. package/front_end/models/live-metrics/LiveMetrics.ts +6 -6
  46. package/front_end/models/live-metrics/web-vitals-injected/spec/spec.ts +5 -5
  47. package/front_end/models/live-metrics/web-vitals-injected/web-vitals-injected.ts +3 -3
  48. package/front_end/models/logs/LogManager.ts +25 -10
  49. package/front_end/models/logs/NetworkLog.ts +24 -19
  50. package/front_end/models/persistence/AutomaticFileSystemWorkspaceBinding.ts +14 -5
  51. package/front_end/models/persistence/IsolatedFileSystem.ts +9 -11
  52. package/front_end/models/persistence/IsolatedFileSystemManager.ts +5 -3
  53. package/front_end/models/persistence/NetworkPersistenceManager.ts +73 -44
  54. package/front_end/models/persistence/PersistenceImpl.ts +4 -5
  55. package/front_end/models/trace/EntityMapper.ts +1 -1
  56. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +9 -9
  57. package/front_end/panels/ai_assistance/ExportConversation.ts +4 -1
  58. package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -2
  59. package/front_end/panels/application/IndexedDBViews.ts +38 -36
  60. package/front_end/panels/application/components/AdsView.ts +4 -0
  61. package/front_end/panels/autofill/AutofillView.ts +1 -1
  62. package/front_end/panels/autofill/autofill-meta.ts +3 -2
  63. package/front_end/panels/elements/AccessibilityTreeUtils.ts +26 -15
  64. package/front_end/panels/elements/AccessibilityTreeView.ts +12 -9
  65. package/front_end/panels/elements/ElementsTreeOutline.ts +5 -0
  66. package/front_end/panels/layer_viewer/LayerTreeOutline.ts +118 -68
  67. package/front_end/panels/layer_viewer/Layers3DView.ts +1 -1
  68. package/front_end/panels/layer_viewer/layerTreeOutline.css +20 -13
  69. package/front_end/panels/layers/LayersPanel.ts +1 -1
  70. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +64 -17
  71. package/front_end/panels/mobile_throttling/throttlingSettingsTab.css +5 -0
  72. package/front_end/panels/network/LinkPreloadGenerator.ts +101 -0
  73. package/front_end/panels/network/NetworkLogView.ts +28 -0
  74. package/front_end/panels/network/network.ts +2 -0
  75. package/front_end/panels/profiler/HeapSnapshotView.ts +121 -33
  76. package/front_end/panels/profiler/ModuleUIStrings.ts +4 -0
  77. package/front_end/panels/sources/ScopeChainSidebarPane.ts +0 -12
  78. package/front_end/panels/timeline/TimelineHistoryManager.ts +38 -37
  79. package/front_end/panels/timeline/TimelinePanel.ts +32 -8
  80. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +7 -6
  81. package/front_end/panels/timeline/components/LiveMetricsView.ts +38 -38
  82. package/front_end/panels/timeline/components/MetricCard.ts +18 -18
  83. package/front_end/panels/timeline/components/liveMetricsView.css +6 -6
  84. package/front_end/panels/timeline/components/metricCard.css +4 -4
  85. package/front_end/panels/whats_new/ReleaseNoteText.ts +3 -3
  86. package/front_end/panels/whats_new/resources/WNDT.md +1 -1
  87. package/front_end/third_party/chromium/README.chromium +1 -1
  88. package/front_end/ui/legacy/InspectorDrawerView.ts +7 -5
  89. package/front_end/ui/legacy/ListWidget.ts +12 -0
  90. package/front_end/ui/legacy/SplitWidget.ts +12 -11
  91. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +23 -19
  92. package/front_end/ui/legacy/components/object_ui/CustomPreviewComponent.ts +8 -1
  93. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +110 -17
  94. package/front_end/ui/legacy/treeoutline.css +1 -1
  95. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  96. package/package.json +4 -3
@@ -0,0 +1,295 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as Common from '../../../core/common/common.js';
6
+ import type {UrlString} from '../../../core/platform/DevToolsPath.js';
7
+ import type * as Platform from '../../../core/platform/platform.js';
8
+ import * as SDK from '../../../core/sdk/sdk.js';
9
+ import * as Tracing from '../../../services/tracing/tracing.js';
10
+ import * as SourceMapScopes from '../../source_map_scopes/source_map_scopes.js';
11
+ import * as Trace from '../../trace/trace.js';
12
+ import {
13
+ type ContextDetail,
14
+ ConversationContext,
15
+ type ConversationSuggestions,
16
+ } from '../agents/AiAgent.js';
17
+ import {extractContextOrigin} from '../AiOrigins.js';
18
+ import {
19
+ PerformanceInsightFormatter,
20
+ } from '../data_formatters/PerformanceInsightFormatter.js';
21
+ import {PerformanceTraceFormatter} from '../data_formatters/PerformanceTraceFormatter.js';
22
+ import type {AICallTree} from '../performance/AICallTree.js';
23
+ import {AgentFocus} from '../performance/AIContext.js';
24
+
25
+ /**
26
+ * The conversation context for AI queries regarding performance traces.
27
+ * Encapsulates the user's active trace selection/focus and handles formatting
28
+ * the context data for the LLM prompt and user-facing accordion disclosures.
29
+ */
30
+ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
31
+ static fromParsedTrace(parsedTrace: Trace.TraceModel.ParsedTrace): PerformanceTraceContext {
32
+ return new PerformanceTraceContext(AgentFocus.fromParsedTrace(parsedTrace));
33
+ }
34
+
35
+ static fromInsight(parsedTrace: Trace.TraceModel.ParsedTrace,
36
+ insight: Trace.Insights.Types.InsightModel): PerformanceTraceContext {
37
+ return new PerformanceTraceContext(AgentFocus.fromInsight(parsedTrace, insight));
38
+ }
39
+
40
+ static fromCallTree(callTree: AICallTree): PerformanceTraceContext {
41
+ return new PerformanceTraceContext(AgentFocus.fromCallTree(callTree));
42
+ }
43
+
44
+ readonly #focus: AgentFocus;
45
+
46
+ constructor(focus: AgentFocus) {
47
+ super();
48
+ this.#focus = focus;
49
+ }
50
+
51
+ /**
52
+ * Returns a PerformanceTraceFormatter configured to resolve function
53
+ * code from source maps using the active page target.
54
+ *
55
+ * Note: Function code resolution from source maps is only supported for fresh
56
+ * recordings (recorded in the current session on the active target page). For
57
+ * imported traces, it returns null to prevent mismatched source resolution.
58
+ */
59
+ createFormatter(): PerformanceTraceFormatter {
60
+ const focus = this.#focus;
61
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
62
+ const formatter = new PerformanceTraceFormatter(focus);
63
+ const isFresh = Tracing.FreshRecording.Tracker.instance().recordingIsFresh(focus.parsedTrace);
64
+
65
+ formatter.resolveFunctionCode = async (url: UrlString, line: number, column: number) => {
66
+ if (!target || !isFresh) {
67
+ return null;
68
+ }
69
+ return await SourceMapScopes.FunctionCodeResolver.getFunctionCodeFromLocation(
70
+ target, url, line, column, {contextLength: 200, contextLineLength: 5, appendProfileData: true});
71
+ };
72
+ return formatter;
73
+ }
74
+
75
+ override getURL(): string {
76
+ const url = this.#focus.parsedTrace.data.Meta.mainFrameURL;
77
+ try {
78
+ new URL(url);
79
+ return url;
80
+ } catch {
81
+ const {min, max} = this.#focus.parsedTrace.data.Meta.traceBounds;
82
+ return `trace-${min}-${max}`;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Returns the origin for a performance trace in the AI context.
88
+ *
89
+ * To prevent cross-origin prompt injection attacks, imported traces
90
+ * are isolated from live pages. We assign them a virtual origin
91
+ * (`imported-trace://${domain}`) so they do not share the origin of live pages
92
+ * (e.g., `https://${domain}`). This forces a conversation reset when transitioning
93
+ * between imported trace data and live pages.
94
+ */
95
+ override getOrigin(): string {
96
+ const parsedTrace = this.#focus.parsedTrace;
97
+ const url = this.getURL();
98
+ const origin = extractContextOrigin(url);
99
+ const isFresh = Tracing.FreshRecording.Tracker.instance().recordingIsFresh(parsedTrace);
100
+ if (!isFresh) {
101
+ const parsed = Common.ParsedURL.ParsedURL.fromString(origin as Platform.DevToolsPath.UrlString);
102
+ return `imported-trace://${parsed ? parsed.domain() : origin}`;
103
+ }
104
+ return origin;
105
+ }
106
+
107
+ override getItem(): AgentFocus {
108
+ return this.#focus;
109
+ }
110
+
111
+ override getTitle(): string {
112
+ const focus = this.#focus;
113
+
114
+ let url = focus.primaryInsightSet?.url;
115
+ if (!url) {
116
+ url = new URL(focus.parsedTrace.data.Meta.mainFrameURL);
117
+ }
118
+
119
+ const parts = [`Trace: ${url.hostname}`];
120
+ if (focus.insight) {
121
+ parts.push(focus.insight.title);
122
+ }
123
+ if (focus.event) {
124
+ parts.push(Trace.Name.forEntry(focus.event));
125
+ }
126
+ if (focus.callTree) {
127
+ const node = focus.callTree.selectedNode ?? focus.callTree.rootNode;
128
+ parts.push(Trace.Name.forEntry(node.event));
129
+ }
130
+ return parts.join(' – ');
131
+ }
132
+
133
+ /**
134
+ * Presents the default suggestions that are shown when the user first clicks
135
+ * "Ask AI".
136
+ */
137
+ override async getSuggestions(): Promise<ConversationSuggestions|undefined> {
138
+ const focus = this.#focus;
139
+
140
+ if (focus.callTree) {
141
+ return [
142
+ {title: 'What\'s the purpose of this work?', jslogContext: 'performance-default'},
143
+ {title: 'Where is time being spent?', jslogContext: 'performance-default'},
144
+ {title: 'How can I optimize this?', jslogContext: 'performance-default'},
145
+ ];
146
+ }
147
+
148
+ if (focus.insight) {
149
+ return new PerformanceInsightFormatter(focus, focus.insight).getSuggestions();
150
+ }
151
+
152
+ const suggestions: ConversationSuggestions =
153
+ [{title: 'What performance issues exist with my page?', jslogContext: 'performance-default'}];
154
+
155
+ const insightSet = focus.primaryInsightSet;
156
+ if (insightSet) {
157
+ const lcp = Trace.Insights.Common.getLCP(insightSet);
158
+ const cls = Trace.Insights.Common.getCLS(insightSet);
159
+ const inp = Trace.Insights.Common.getINP(insightSet);
160
+
161
+ const ModelHandlers = Trace.Handlers.ModelHandlers;
162
+ const GOOD = Trace.Handlers.ModelHandlers.PageLoadMetrics.ScoreClassification.GOOD;
163
+
164
+ const poorMetrics = new Set<Trace.Insights.Types.InsightKeys>();
165
+
166
+ if (lcp && ModelHandlers.PageLoadMetrics.scoreClassificationForLargestContentfulPaint(lcp.value) !== GOOD) {
167
+ suggestions.push({title: 'How can I improve LCP?', jslogContext: 'performance-default'});
168
+ poorMetrics.add(Trace.Insights.Types.InsightKeys.LCP_BREAKDOWN);
169
+ poorMetrics.add(Trace.Insights.Types.InsightKeys.LCP_DISCOVERY);
170
+ }
171
+ if (inp && ModelHandlers.UserInteractions.scoreClassificationForInteractionToNextPaint(inp.value) !== GOOD) {
172
+ suggestions.push({title: 'How can I improve INP?', jslogContext: 'performance-default'});
173
+ poorMetrics.add(Trace.Insights.Types.InsightKeys.INP_BREAKDOWN);
174
+ }
175
+ if (cls && ModelHandlers.LayoutShifts.scoreClassificationForLayoutShift(cls.value) !== GOOD) {
176
+ suggestions.push({title: 'How can I improve CLS?', jslogContext: 'performance-default'});
177
+ poorMetrics.add(Trace.Insights.Types.InsightKeys.CLS_CULPRITS);
178
+ }
179
+
180
+ // Add up to 4 suggestions total (including those already added) from the top failing insights
181
+ // that aren't already covered by CWV suggestions.
182
+ const additionalSuggestionsRequired = Math.max(0, 4 - suggestions.length);
183
+ if (additionalSuggestionsRequired > 0) {
184
+ const failingInsightSuggestions =
185
+ Object.values(insightSet.model)
186
+ .filter(model => {
187
+ return model.state !== 'pass' && Trace.Insights.Common.isInsightKey(model.insightKey) &&
188
+ !poorMetrics.has(model.insightKey);
189
+ })
190
+ .map(model => new PerformanceInsightFormatter(focus, model).getSuggestions().at(-1))
191
+ .filter((suggestion): suggestion is ConversationSuggestions[number] => !!suggestion)
192
+ .slice(0, additionalSuggestionsRequired);
193
+ suggestions.push(...failingInsightSuggestions);
194
+ }
195
+ }
196
+
197
+ return suggestions;
198
+ }
199
+
200
+ /**
201
+ * Returns a markdown-formatted payload containing the trace data facts
202
+ * (summary, critical requests, activities, third-party code, and longest tasks)
203
+ * to be included directly in the LLM's prompt.
204
+ *
205
+ * Invariant: The content returned here must align with the user-facing details
206
+ * returned by `getUserFacingDetails()` to ensure complete data transparency.
207
+ */
208
+ override async getPromptDetails(): Promise<string|null> {
209
+ const formatter = this.createFormatter();
210
+
211
+ const details: string[] = [];
212
+
213
+ const traceSummary = formatter.formatTraceSummary();
214
+ if (traceSummary) {
215
+ details.push(`Trace summary:\n${traceSummary}`);
216
+ }
217
+
218
+ const criticalRequests = await formatter.formatCriticalRequests();
219
+ if (criticalRequests) {
220
+ details.push(criticalRequests);
221
+ }
222
+
223
+ const mainThreadBottomUp = await formatter.formatMainThreadBottomUpSummary();
224
+ if (mainThreadBottomUp) {
225
+ details.push(mainThreadBottomUp);
226
+ }
227
+
228
+ const thirdPartySummary = await formatter.formatThirdPartySummary();
229
+ if (thirdPartySummary) {
230
+ details.push(thirdPartySummary);
231
+ }
232
+
233
+ const longestTasks = await formatter.formatLongestTasks();
234
+ if (longestTasks) {
235
+ details.push(longestTasks);
236
+ }
237
+
238
+ return details.length > 0 ? details.join('\n\n') : null;
239
+ }
240
+
241
+ /**
242
+ * Returns structured trace context details to be displayed to the user in the UI
243
+ * (under the "Analyzing data" disclosure accordion).
244
+ *
245
+ * Invariant: The details shown here must correspond exactly to the data sent to
246
+ * the LLM prompt via `getPromptDetails()`.
247
+ */
248
+ override async getUserFacingDetails(): Promise<[ContextDetail, ...ContextDetail[]]|null> {
249
+ const formatter = this.createFormatter();
250
+
251
+ const details: ContextDetail[] = [];
252
+
253
+ const traceSummary = formatter.formatTraceSummary();
254
+ if (traceSummary) {
255
+ details.push({
256
+ title: 'Trace summary',
257
+ text: traceSummary,
258
+ });
259
+ }
260
+
261
+ const criticalRequests = await formatter.formatCriticalRequests();
262
+ if (criticalRequests) {
263
+ details.push({
264
+ title: 'Critical requests',
265
+ text: criticalRequests,
266
+ });
267
+ }
268
+
269
+ const mainThreadBottomUp = await formatter.formatMainThreadBottomUpSummary();
270
+ if (mainThreadBottomUp) {
271
+ details.push({
272
+ title: 'Main thread activities',
273
+ text: mainThreadBottomUp,
274
+ });
275
+ }
276
+
277
+ const thirdPartySummary = await formatter.formatThirdPartySummary();
278
+ if (thirdPartySummary) {
279
+ details.push({
280
+ title: 'Third party summary',
281
+ text: thirdPartySummary,
282
+ });
283
+ }
284
+
285
+ const longestTasks = await formatter.formatLongestTasks();
286
+ if (longestTasks) {
287
+ details.push({
288
+ title: 'Longest tasks',
289
+ text: longestTasks,
290
+ });
291
+ }
292
+
293
+ return details.length > 0 ? (details as [ContextDetail, ...ContextDetail[]]) : null;
294
+ }
295
+ }
@@ -3,7 +3,7 @@ Content:
3
3
  ## Insight Title: LCP breakdown
4
4
 
5
5
  ## Insight Summary:
6
- This insight is used to analyze the time spent that contributed to the final LCP time and identify which of the 4 phases (or 2 if there was no LCP resource) are contributing most to the delay in rendering the LCP element.
6
+ This insight is used to analyze the time spent that contributed to the final LCP time and identify which of the 4 subparts (or 2 if there was no LCP resource) are contributing most to the delay in rendering the LCP element.
7
7
 
8
8
  ## Detailed analysis:
9
9
  The Largest Contentful Paint (LCP) time for this navigation was 240 ms.
@@ -45,7 +45,7 @@ Response headers
45
45
  - server: Apache
46
46
  - access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept, Authorization
47
47
 
48
- We can break this time down into the 4 phases that combine to make the LCP time:
48
+ We can break this time down into the 4 subparts that combine to make the LCP time:
49
49
 
50
50
  - Time to first byte: 2 ms (0.7% of total LCP time)
51
51
  - Resource load delay: 53 ms (22.1% of total LCP time)
@@ -65,7 +65,7 @@ Content:
65
65
  ## Insight Title: LCP breakdown
66
66
 
67
67
  ## Insight Summary:
68
- This insight is used to analyze the time spent that contributed to the final LCP time and identify which of the 4 phases (or 2 if there was no LCP resource) are contributing most to the delay in rendering the LCP element.
68
+ This insight is used to analyze the time spent that contributed to the final LCP time and identify which of the 4 subparts (or 2 if there was no LCP resource) are contributing most to the delay in rendering the LCP element.
69
69
 
70
70
  ## Detailed analysis:
71
71
  The Largest Contentful Paint (LCP) time for this navigation was 129 ms.
@@ -91,7 +91,7 @@ From a service worker: No
91
91
  Initiators (root request to the request that directly loaded this one): none
92
92
 
93
93
 
94
- We can break this time down into the 4 phases that combine to make the LCP time:
94
+ We can break this time down into the 4 subparts that combine to make the LCP time:
95
95
 
96
96
  - Time to first byte: 8 ms (6.1% of total LCP time)
97
97
  - Resource load delay: 33 ms (25.7% of total LCP time)
@@ -106,18 +106,18 @@ We can break this time down into the 4 phases that combine to make the LCP time:
106
106
  - https://web.dev/articles/optimize-lcp
107
107
  === end content
108
108
 
109
- Title: PerformanceInsightFormatter LCP breakdown formats correctly when the LCP is text based and has no load delay or time phases
109
+ Title: PerformanceInsightFormatter LCP breakdown formats correctly when the LCP is text based and has no load delay or time subparts
110
110
  Content:
111
111
  ## Insight Title: LCP breakdown
112
112
 
113
113
  ## Insight Summary:
114
- This insight is used to analyze the time spent that contributed to the final LCP time and identify which of the 4 phases (or 2 if there was no LCP resource) are contributing most to the delay in rendering the LCP element.
114
+ This insight is used to analyze the time spent that contributed to the final LCP time and identify which of the 4 subparts (or 2 if there was no LCP resource) are contributing most to the delay in rendering the LCP element.
115
115
 
116
116
  ## Detailed analysis:
117
117
  The Largest Contentful Paint (LCP) time for this navigation was 106 ms.
118
118
  The LCP element is text and was not fetched from the network.
119
119
 
120
- We can break this time down into the 2 phases that combine to make the LCP time:
120
+ We can break this time down into the 2 subparts that combine to make the LCP time:
121
121
 
122
122
  - Time to first byte: 6 ms (5.7% of total LCP time)
123
123
  - Element render delay: 100 ms (94.3% of total LCP time)
@@ -561,15 +561,15 @@ Interaction to Next Paint (INP) is a metric that tracks the responsiveness of th
561
561
  - Needs improvement: more than 200 milliseconds and 500 milliseconds or less.
562
562
  - Bad: over 500 milliseconds.
563
563
 
564
- For a given slow interaction, we can break it down into 3 phases:
564
+ For a given slow interaction, we can break it down into 3 subparts:
565
565
  1. Input delay: starts when the user initiates an interaction with the page, and ends when the event callbacks for the interaction begin to run.
566
566
  2. Processing duration: the time it takes for the event callbacks to run to completion.
567
567
  3. Presentation delay: the time it takes for the browser to present the next frame which contains the visual result of the interaction.
568
568
 
569
- The sum of these three phases is the total latency. It is important to optimize each of these phases to ensure interactions take as little time as possible. Focusing on the phase that has the largest score is a good way to start optimizing.
569
+ The sum of these three subparts is the total latency. It is important to optimize each of these subparts to ensure interactions take as little time as possible. Focusing on the subpart that has the largest score is a good way to start optimizing.
570
570
 
571
571
  ## Detailed analysis:
572
- The longest interaction on the page was a `click` which had a total duration of `980 ms`. The timings of each of the three phases were:
572
+ The longest interaction on the page was a `click` which had a total duration of `980 ms`. The timings of each of the three subparts were:
573
573
 
574
574
  1. Input delay: 1 ms
575
575
  2. Processing duration: 977 ms
@@ -177,7 +177,7 @@ export class PerformanceInsightFormatter {
177
177
  ];
178
178
  case 'LCPBreakdown':
179
179
  return [
180
- {title: 'Help me optimize my LCP score'}, {title: 'Which LCP phase was most problematic?'},
180
+ {title: 'Help me optimize my LCP score'}, {title: 'Which LCP subpart was most problematic?'},
181
181
  {title: 'What can I do to reduce the LCP time for this page load?'}
182
182
  ];
183
183
  case 'NetworkDependencyTree':
@@ -575,7 +575,7 @@ The following images could be optimized:\n\n${imageDetails}`;
575
575
 
576
576
  const inpInfoForEvent =
577
577
  `The longest interaction on the page was a \`${event.type}\` which had a total duration of \`${
578
- this.#formatMicro(event.dur)}\`. The timings of each of the three phases were:
578
+ this.#formatMicro(event.dur)}\`. The timings of each of the three subparts were:
579
579
 
580
580
  1. Input delay: ${this.#formatMicro(event.inputDelay)}
581
581
  2. Processing duration: ${this.#formatMicro(event.mainThreadHandling)}
@@ -600,20 +600,21 @@ The following images could be optimized:\n\n${imageDetails}`;
600
600
  // Note that we expect every trace + LCP to have TTFB + Render delay, but
601
601
  // very old traces are missing the data, so we have to code defensively
602
602
  // in case the subparts are not present.
603
- const phaseBulletPoints: Array<{name: string, value: string, percentage: string}> = [];
603
+ const subpartBulletPoints: Array<{name: string, value: string, percentage: string}> = [];
604
604
 
605
605
  Object.values(subparts).forEach((subpart: Trace.Insights.Models.LCPBreakdown.Subpart) => {
606
- const phaseMilli = Trace.Helpers.Timing.microToMilli(subpart.range);
607
- const percentage = (phaseMilli / lcpMs * 100).toFixed(1);
608
- phaseBulletPoints.push({name: subpart.label, value: this.#formatMilli(phaseMilli), percentage});
606
+ const subpartMilli = Trace.Helpers.Timing.microToMilli(subpart.range);
607
+ const percentage = (subpartMilli / lcpMs * 100).toFixed(1);
608
+ subpartBulletPoints.push({name: subpart.label, value: this.#formatMilli(subpartMilli), percentage});
609
609
  });
610
610
 
611
611
  return `${this.#lcpMetricSharedContext()}
612
612
 
613
- We can break this time down into the ${phaseBulletPoints.length} phases that combine to make the LCP time:
613
+ We can break this time down into the ${subpartBulletPoints.length} subparts that combine to make the LCP time:
614
614
 
615
615
  ${
616
- phaseBulletPoints.map(phase => `- ${phase.name}: ${phase.value} (${phase.percentage}% of total LCP time)`)
616
+ subpartBulletPoints
617
+ .map(subpart => `- ${subpart.name}: ${subpart.value} (${subpart.percentage}% of total LCP time)`)
617
618
  .join('\n')}`;
618
619
  }
619
620
 
@@ -1139,12 +1140,12 @@ ${this.#links()}`;
1139
1140
  - Needs improvement: more than 200 milliseconds and 500 milliseconds or less.
1140
1141
  - Bad: over 500 milliseconds.
1141
1142
 
1142
- For a given slow interaction, we can break it down into 3 phases:
1143
+ For a given slow interaction, we can break it down into 3 subparts:
1143
1144
  1. Input delay: starts when the user initiates an interaction with the page, and ends when the event callbacks for the interaction begin to run.
1144
1145
  2. Processing duration: the time it takes for the event callbacks to run to completion.
1145
1146
  3. Presentation delay: the time it takes for the browser to present the next frame which contains the visual result of the interaction.
1146
1147
 
1147
- The sum of these three phases is the total latency. It is important to optimize each of these phases to ensure interactions take as little time as possible. Focusing on the phase that has the largest score is a good way to start optimizing.`;
1148
+ The sum of these three subparts is the total latency. It is important to optimize each of these subparts to ensure interactions take as little time as possible. Focusing on the subpart that has the largest score is a good way to start optimizing.`;
1148
1149
  case 'LCPDiscovery':
1149
1150
  return `This insight analyzes the time taken to discover the LCP resource and request it on the network. It only applies if the LCP element was a resource like an image that has to be fetched over the network. There are 3 checks this insight makes:
1150
1151
  1. Did the resource have \`fetchpriority=high\` applied?
@@ -1153,7 +1154,7 @@ The sum of these three phases is the total latency. It is important to optimize
1153
1154
 
1154
1155
  It is important that all of these checks pass to minimize the delay between the initial page load and the LCP resource being loaded.`;
1155
1156
  case 'LCPBreakdown':
1156
- return 'This insight is used to analyze the time spent that contributed to the final LCP time and identify which of the 4 phases (or 2 if there was no LCP resource) are contributing most to the delay in rendering the LCP element.';
1157
+ return 'This insight is used to analyze the time spent that contributed to the final LCP time and identify which of the 4 subparts (or 2 if there was no LCP resource) are contributing most to the delay in rendering the LCP element.';
1157
1158
  case 'NetworkDependencyTree':
1158
1159
  return `This insight analyzes the network dependency tree to identify:
1159
1160
  - The maximum critical path latency (the longest chain of network requests that the browser must download before it can render the page).
@@ -486,7 +486,7 @@ Available insights:
486
486
  description: Each [subpart has specific improvement strategies](https://developer.chrome.com/docs/performance/insights/lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.
487
487
  relevant trace bounds: {min: 1020034834921µs, max: 1020034953358µs}
488
488
  example question: Help me optimize my LCP score
489
- example question: Which LCP phase was most problematic?
489
+ example question: Which LCP subpart was most problematic?
490
490
  example question: What can I do to reduce the LCP time for this page load?
491
491
  - insight name: DocumentLatency
492
492
  description: Your first network request is the most important. [Reduce its latency](https://developer.chrome.com/docs/performance/insights/document-latency) by avoiding redirects, ensuring a fast server response, and enabling text compression.
@@ -534,7 +534,7 @@ Available insights:
534
534
  description: Each [subpart has specific improvement strategies](https://developer.chrome.com/docs/performance/insights/lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.
535
535
  relevant trace bounds: {min: 157423488682µs, max: 157423953162µs}
536
536
  example question: Help me optimize my LCP score
537
- example question: Which LCP phase was most problematic?
537
+ example question: Which LCP subpart was most problematic?
538
538
  example question: What can I do to reduce the LCP time for this page load?
539
539
  - insight name: CLSCulprits
540
540
  description: Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://developer.chrome.com/docs/performance/insights/cls-culprit), such as elements being added, removed, or their fonts changing as the page loads.
@@ -603,7 +603,7 @@ Available insights:
603
603
  description: Each [subpart has specific improvement strategies](https://developer.chrome.com/docs/performance/insights/lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.
604
604
  relevant trace bounds: {min: 171607579779µs, max: 171608897210µs}
605
605
  example question: Help me optimize my LCP score
606
- example question: Which LCP phase was most problematic?
606
+ example question: Which LCP subpart was most problematic?
607
607
  example question: What can I do to reduce the LCP time for this page load?
608
608
  - insight name: RenderBlocking
609
609
  description: Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://developer.chrome.com/docs/performance/insights/render-blocking) can move these network requests out of the critical path.
@@ -631,7 +631,7 @@ Available insights:
631
631
  description: Each [subpart has specific improvement strategies](https://developer.chrome.com/docs/performance/insights/lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.
632
632
  relevant trace bounds: {min: 171613750571µs, max: 171615060776µs}
633
633
  example question: Help me optimize my LCP score
634
- example question: Which LCP phase was most problematic?
634
+ example question: Which LCP subpart was most problematic?
635
635
  example question: What can I do to reduce the LCP time for this page load?
636
636
  - insight name: RenderBlocking
637
637
  description: Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://developer.chrome.com/docs/performance/insights/render-blocking) can move these network requests out of the critical path.
@@ -686,7 +686,7 @@ Available insights:
686
686
  description: Each [subpart has specific improvement strategies](https://developer.chrome.com/docs/performance/insights/lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.
687
687
  relevant trace bounds: {min: 59728649746µs, max: 59729312744µs}
688
688
  example question: Help me optimize my LCP score
689
- example question: Which LCP phase was most problematic?
689
+ example question: Which LCP subpart was most problematic?
690
690
  example question: What can I do to reduce the LCP time for this page load?
691
691
  - insight name: LCPDiscovery
692
692
  description: [Optimize LCP](https://developer.chrome.com/docs/performance/insights/lcp-discovery) by making the LCP image discoverable from the HTML immediately, and avoiding lazy-loading
@@ -770,7 +770,7 @@ Available insights:
770
770
  description: Each [subpart has specific improvement strategies](https://developer.chrome.com/docs/performance/insights/lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.
771
771
  relevant trace bounds: {min: 59728649746µs, max: 59729312744µs}
772
772
  example question: Help me optimize my LCP score
773
- example question: Which LCP phase was most problematic?
773
+ example question: Which LCP subpart was most problematic?
774
774
  example question: What can I do to reduce the LCP time for this page load?
775
775
  - insight name: LCPDiscovery
776
776
  description: [Optimize LCP](https://developer.chrome.com/docs/performance/insights/lcp-discovery) by making the LCP image discoverable from the HTML immediately, and avoiding lazy-loading
@@ -3,11 +3,13 @@ name: accessibility
3
3
  description: Accessibility audits and report querying.
4
4
  allowed-tools:
5
5
  - getLighthouseAudits
6
- - resolveLighthousePath
6
+ - resolveDevtoolsNodePath
7
7
  - getStyles
8
+ - getElementAccessibilityDetails
8
9
  ---
9
10
  You are an expert accessibility debugging assistant.
10
11
  Use getLighthouseAudits to query details from the active report.
11
12
 
12
- * ALWAYS use resolveLighthousePath to resolve failing element paths to backend node IDs.
13
+ * ALWAYS use resolveDevtoolsNodePath to resolve failing element paths to backend node IDs.
13
14
  * Once resolved, use getStyles on the backend node ID to inspect layout and styling properties.
15
+ * Use getElementAccessibilityDetails to query detailed accessibility properties (ARIA properties, role, name, focus state) for a resolved element backend node ID.
@@ -0,0 +1,142 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as Host from '../../../core/host/host.js';
6
+ import * as i18n from '../../../core/i18n/i18n.js';
7
+ import * as SDK from '../../../core/sdk/sdk.js';
8
+ import type * as Protocol from '../../../generated/protocol.js';
9
+ import type {FunctionCallHandlerResult} from '../agents/AiAgent.js';
10
+ import {DOMNodeContext} from '../contexts/DOMNodeContext.js';
11
+
12
+ import {
13
+ type BaseToolCapability,
14
+ type OriginLockCapability,
15
+ type TargetCapability,
16
+ type Tool,
17
+ type ToolArgs,
18
+ ToolName,
19
+ } from './Tool.js';
20
+
21
+ /**
22
+ * Arguments for getting accessibility details of an element.
23
+ */
24
+ export interface GetElementAccessibilityDetailsArgs extends ToolArgs {
25
+ /**
26
+ * The backend node ID of the element to inspect.
27
+ */
28
+ element: number;
29
+ explanation: string;
30
+ }
31
+
32
+ /**
33
+ * A tool that retrieves fine-grained accessibility properties (role, name, ARIA properties, focus state)
34
+ * for a resolved element backend node ID. It also returns a DOM snapshot of the element's subtree.
35
+ */
36
+ export class GetElementAccessibilityDetailsTool implements
37
+ Tool<GetElementAccessibilityDetailsArgs, string, BaseToolCapability&TargetCapability&OriginLockCapability> {
38
+ readonly name = ToolName.GET_ELEMENT_ACCESSIBILITY_DETAILS;
39
+ readonly description =
40
+ 'Get detailed accessibility information for an element on the inspected page by its backend node ID.';
41
+
42
+ readonly parameters: Host.AidaClient.FunctionObjectParam<keyof GetElementAccessibilityDetailsArgs> = {
43
+ type: Host.AidaClient.ParametersTypes.OBJECT,
44
+ description: 'Arguments for getting element accessibility details.',
45
+ nullable: false,
46
+ properties: {
47
+ explanation: {
48
+ type: Host.AidaClient.ParametersTypes.STRING,
49
+ description: 'Reason for requesting accessibility details.',
50
+ nullable: false,
51
+ },
52
+ element: {
53
+ type: Host.AidaClient.ParametersTypes.INTEGER,
54
+ description: 'The backend node ID of the element.',
55
+ nullable: false,
56
+ },
57
+ },
58
+ required: ['explanation', 'element'],
59
+ };
60
+
61
+ displayInfoFromArgs(params: GetElementAccessibilityDetailsArgs): {
62
+ title: string,
63
+ thought: string,
64
+ action: string,
65
+ } {
66
+ return {
67
+ title: 'Reading accessibility details',
68
+ thought: params.explanation,
69
+ action: `getElementAccessibilityDetails(${params.element})`,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Handles the request to retrieve accessibility details.
75
+ *
76
+ * Resolves the element backend node ID, validates its origin against the locked origin,
77
+ * requests the AX subtree via AccessibilityModel, and maps the relevant attributes.
78
+ */
79
+ async handler(
80
+ params: GetElementAccessibilityDetailsArgs,
81
+ context: BaseToolCapability&TargetCapability&OriginLockCapability,
82
+ ): Promise<FunctionCallHandlerResult<string>> {
83
+ const establishedOrigin = context.getEstablishedOrigin();
84
+ if (!establishedOrigin) {
85
+ return {error: 'Error: Origin lock is not established.'};
86
+ }
87
+
88
+ const target = context.getTarget();
89
+ if (!target) {
90
+ return {error: 'Error: Inspected target not found.'};
91
+ }
92
+
93
+ // Resolve the backend node ID to an SDK DOMNode. We use DeferredDOMNode
94
+ // because the backend node ID is the stable identifier received from external
95
+ // sources (like Lighthouse audits), and we need to fetch the local node object to query it.
96
+ const deferredNode = new SDK.DOMModel.DeferredDOMNode(target, params.element as Protocol.DOM.BackendNodeId);
97
+ const resolved = await deferredNode.resolvePromise();
98
+ if (!resolved) {
99
+ return {error: 'Error: Could not resolve element by ID.'};
100
+ }
101
+
102
+ const nodeContext = new DOMNodeContext(resolved);
103
+ // Security check: Ensure the element matches the active conversation's origin lock.
104
+ if (!nodeContext.isOriginAllowed(establishedOrigin)) {
105
+ return {error: 'Error: Node does not belong to the locked origin.'};
106
+ }
107
+
108
+ const axModel = target.model(SDK.AccessibilityModel.AccessibilityModel);
109
+ if (!axModel) {
110
+ return {error: 'Error: Accessibility model not found.'};
111
+ }
112
+
113
+ // Accessibility data is not loaded by default for all nodes to optimize performance.
114
+ // We must explicitly request and load the accessibility subtree for the resolved node before querying.
115
+ await axModel.requestAndLoadSubTreeToNode(resolved);
116
+ const axNode = axModel.axNodeForDOMNode(resolved);
117
+ if (!axNode) {
118
+ return {error: 'Error: AX node details not found.'};
119
+ }
120
+
121
+ const properties = {
122
+ role: axNode.role()?.value,
123
+ name: axNode.name()?.value,
124
+ properties: axNode.properties()?.map(p => ({name: p.name, value: p.value?.value})) ?? [],
125
+ };
126
+
127
+ // Take a snapshot of the resolved node's DOM structure. This is required
128
+ // by the DOM_TREE UI widget to render the element's local tree in the AI response panel.
129
+ const snapshot = await resolved.takeSnapshot();
130
+ return {
131
+ result: JSON.stringify(properties, null, 2),
132
+ widgets: [{
133
+ name: 'DOM_TREE',
134
+ data: {
135
+ root: snapshot,
136
+ title: i18n.i18n.lockedString('Element details'),
137
+ accessibleRevealLabel: i18n.i18n.lockedString('Reveal element'),
138
+ },
139
+ }],
140
+ };
141
+ }
142
+ }
@@ -105,7 +105,7 @@ export class GetStylesTool implements
105
105
  return {error: 'Error: Could not find the element with uid=' + uid};
106
106
  }
107
107
  const newContext = new DOMNodeContext(resolved);
108
- if (establishedOrigin !== newContext.getOrigin()) {
108
+ if (!newContext.isOriginAllowed(establishedOrigin)) {
109
109
  return {error: 'Error: Node does not belong to the current origin.'};
110
110
  }
111
111
  const styles = await resolved.domModel().cssModel().getComputedStyle(resolved.id);