chrome-devtools-frontend 1.0.1696961 → 1.0.1697998

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 (121) hide show
  1. package/.geminiignore +0 -1
  2. package/.stylelintignore +2 -0
  3. package/front_end/core/common/Color.ts +5 -2
  4. package/front_end/core/common/VersionController.ts +5 -1
  5. package/front_end/core/host/InspectorFrontendHostAPI.ts +16 -3
  6. package/front_end/core/host/InspectorFrontendHostStub.ts +3 -2
  7. package/front_end/core/host/ResourceLoader.ts +1 -2
  8. package/front_end/core/platform/api/HostRuntime.ts +1 -0
  9. package/front_end/core/platform/browser/HostRuntime.ts +4 -0
  10. package/front_end/core/platform/node/HostRuntime.ts +5 -0
  11. package/front_end/core/root/Runtime.ts +5 -0
  12. package/front_end/core/sdk/AnimationModel.ts +9 -1
  13. package/front_end/core/sdk/DOMModel.ts +13 -0
  14. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +9 -6
  15. package/front_end/core/sdk/SourceMapScopesInfo.ts +3 -1
  16. package/front_end/foundation/Universe.ts +8 -0
  17. package/front_end/generated/InspectorBackendCommands.ts +4 -1
  18. package/front_end/generated/protocol-mapping.d.ts +19 -0
  19. package/front_end/generated/protocol-proxy-api.d.ts +15 -0
  20. package/front_end/generated/protocol.ts +43 -2
  21. package/front_end/models/ai_assistance/AiAgent2.ts +20 -18
  22. package/front_end/models/ai_assistance/AiConversation.ts +38 -12
  23. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +9 -2
  24. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +26 -0
  25. package/front_end/models/ai_assistance/agents/StylingAgent.ts +13 -4
  26. package/front_end/models/ai_assistance/ai_assistance.ts +4 -4
  27. package/front_end/models/ai_assistance/skills/performance.md +4 -3
  28. package/front_end/models/ai_assistance/tools/CookieUtils.ts +10 -8
  29. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +1 -1
  30. package/front_end/models/ai_assistance/tools/GetElementAccessibilityDetails.ts +1 -3
  31. package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +9 -8
  32. package/front_end/models/ai_assistance/tools/GetSourceContent.ts +5 -6
  33. package/front_end/models/ai_assistance/tools/GetStorageValues.ts +6 -25
  34. package/front_end/models/ai_assistance/tools/GetStyles.ts +1 -3
  35. package/front_end/models/ai_assistance/tools/{GetFunctionCode.ts → GetTraceFunctionCode.ts} +26 -13
  36. package/front_end/models/ai_assistance/tools/{GetResourceContent.ts → GetTraceResourceContent.ts} +31 -11
  37. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +7 -6
  38. package/front_end/models/ai_assistance/tools/ListPageOrigins.ts +8 -5
  39. package/front_end/models/ai_assistance/tools/ListSources.ts +8 -10
  40. package/front_end/models/ai_assistance/tools/ListStorageKeys.ts +6 -25
  41. package/front_end/models/ai_assistance/tools/README.md +5 -3
  42. package/front_end/models/ai_assistance/tools/ResolveDevtoolsNodePath.ts +1 -2
  43. package/front_end/models/ai_assistance/tools/Tool.ts +54 -16
  44. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +6 -6
  45. package/front_end/models/change_tracker/ChangeTracker.ts +89 -0
  46. package/front_end/models/change_tracker/change_tracker.ts +9 -0
  47. package/front_end/models/elements/DOMChanges.ts +238 -0
  48. package/front_end/models/elements/elements.ts +2 -0
  49. package/front_end/models/issues_manager/MarkdownIssueDescription.ts +2 -2
  50. package/front_end/models/live-metrics/LiveMetrics.ts +1 -2
  51. package/front_end/models/trace/ModelImpl.ts +0 -6
  52. package/front_end/models/trace/Processor.ts +4 -7
  53. package/front_end/models/trace/helpers/Timing.ts +2 -1
  54. package/front_end/models/trace/types/Overlays.ts +12 -1
  55. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +67 -19
  56. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +3 -1
  57. package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +25 -5
  58. package/front_end/panels/application/IndexedDBViews.ts +1 -1
  59. package/front_end/panels/browser_debugger/ObjectEventListenersSidebarPane.ts +2 -3
  60. package/front_end/panels/elements/DOMTreeContextMenu.ts +16 -23
  61. package/front_end/panels/elements/ElementsTreeElement.ts +384 -79
  62. package/front_end/panels/elements/ElementsTreeOutline.ts +541 -82
  63. package/front_end/panels/elements/EventListenersWidget.ts +0 -1
  64. package/front_end/panels/event_listeners/EventListenersView.ts +241 -297
  65. package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorController.ts +1 -1
  66. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -2
  67. package/front_end/panels/network/NetworkConfigView.ts +141 -116
  68. package/front_end/panels/network/NetworkPanel.ts +140 -52
  69. package/front_end/panels/network/RequestHeadersView.ts +65 -32
  70. package/front_end/panels/network/components/RequestHeadersView.css +7 -1
  71. package/front_end/panels/network/forward/BackendLinking.ts +52 -0
  72. package/front_end/panels/network/forward/forward.ts +2 -0
  73. package/front_end/panels/settings/BackendLinkingSettingsTab.ts +308 -0
  74. package/front_end/panels/settings/backendLinkingSettingsTab.css +78 -0
  75. package/front_end/panels/settings/settings-meta.ts +25 -0
  76. package/front_end/panels/settings/settings.ts +2 -0
  77. package/front_end/panels/timeline/README.md +3 -2
  78. package/front_end/panels/timeline/TimelineFlameChartView.ts +23 -2
  79. package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
  80. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +64 -4
  81. package/front_end/panels/timeline/timelineFlameChartView.css +47 -0
  82. package/front_end/panels/whats_new/ReleaseNoteText.ts +8 -8
  83. package/front_end/panels/whats_new/resources/WNDT.md +6 -6
  84. package/front_end/third_party/chromium/README.chromium +1 -1
  85. package/front_end/third_party/puppeteer/README.chromium +2 -2
  86. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +6 -0
  87. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +5 -8
  88. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Coverage.d.ts +6 -0
  89. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Coverage.d.ts.map +1 -1
  90. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Coverage.js.map +1 -1
  91. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/ScreenRecording.d.ts.map +1 -1
  92. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/ScreenRecording.js +1 -5
  93. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/ScreenRecording.js.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
  95. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
  96. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  98. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  99. package/front_end/third_party/puppeteer/package/lib/types.d.ts +6 -0
  100. package/front_end/third_party/puppeteer/package/package.json +2 -2
  101. package/front_end/third_party/puppeteer/package/src/cdp/Coverage.ts +6 -0
  102. package/front_end/third_party/puppeteer/package/src/cdp/ScreenRecording.ts +3 -7
  103. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  104. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  105. package/front_end/third_party/web-vitals/README.chromium +2 -2
  106. package/front_end/third_party/web-vitals/package/dist/modules/attribution/onINP.js +8 -5
  107. package/front_end/third_party/web-vitals/package/package.json +1 -1
  108. package/front_end/third_party/web-vitals/package/src/attribution/onINP.ts +8 -4
  109. package/front_end/third_party/web-vitals/rebuild.sh +2 -2
  110. package/front_end/ui/comments/CommentOverlayManager.ts +169 -40
  111. package/front_end/ui/comments/CommentThreadWidget.ts +221 -0
  112. package/front_end/ui/comments/CommentsOverlayWidget.ts +124 -12
  113. package/front_end/ui/comments/commentThreadWidget.css +173 -0
  114. package/front_end/ui/comments/comments.ts +2 -0
  115. package/front_end/ui/comments/commentsOverlay.css +6 -0
  116. package/front_end/ui/legacy/ContextMenu.ts +19 -0
  117. package/front_end/ui/legacy/SoftContextMenu.ts +6 -0
  118. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +1 -1
  119. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +66 -22
  120. package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
  121. package/package.json +9 -8
@@ -0,0 +1,238 @@
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 Platform from '../../core/platform/platform.js';
6
+ import type * as SDK from '../../core/sdk/sdk.js';
7
+ import type * as ChangeTracker from '../change_tracker/change_tracker.js';
8
+ import type * as CommentManager from '../comment_manager/comment_manager.js';
9
+
10
+ type Tracker = ChangeTracker.ChangeTracker.ChangeTracker|undefined;
11
+
12
+ /**
13
+ * Maximum number of characters of page controlled data (tag names, attribute names and values,
14
+ * text node values and HTML) that a change description embeds. The inspected page fully controls
15
+ * these strings and `Changed HTML from ... to ...` in particular can otherwise hold on to the
16
+ * entire subtree of an element.
17
+ */
18
+ export const MAX_VALUE_LENGTH = 100;
19
+
20
+ /** Shortens page controlled data so that both its start and its end stay recognizable. */
21
+ function trimValue(value: string): string {
22
+ return Platform.StringUtilities.trimMiddle(value, MAX_VALUE_LENGTH);
23
+ }
24
+
25
+ function buildAnchor(node: SDK.DOMModel.DOMNode,
26
+ selector = 'element'): CommentManager.CommentManager.CommentAnchorSignature {
27
+ return {
28
+ vePath: 'Panel: elements > Tree: elements > TreeItem',
29
+ textSignature: selector,
30
+ node: {
31
+ backendNodeId: node.backendNodeId(),
32
+ targetId: node.domModel().target().id(),
33
+ },
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Parses a raw attribute string (e.g. 'class="btn"', 'data-id=123', or 'disabled')
39
+ * into its attribute name and unquoted value for single-attribute inline edits.
40
+ */
41
+ function parseAttributeText(text: string): {name: string, value: string} {
42
+ const trimmed = text.trim();
43
+ const eqIdx = trimmed.indexOf('=');
44
+ if (eqIdx === -1) {
45
+ return {name: trimmed, value: ''};
46
+ }
47
+ const name = trimmed.slice(0, eqIdx).trim();
48
+ let value = trimmed.slice(eqIdx + 1).trim();
49
+ if (value.length >= 2 &&
50
+ ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) {
51
+ value = value.slice(1, -1);
52
+ }
53
+ return {name, value};
54
+ }
55
+
56
+ function describeAddAttribute(attributeName: string, value?: string): string {
57
+ if (value !== undefined && value !== '') {
58
+ return `Added attribute ${trimValue(attributeName)}="${trimValue(value)}"`;
59
+ }
60
+ return `Added attribute "${trimValue(attributeName)}"`;
61
+ }
62
+
63
+ function describeRemoveAttribute(attributeName: string): string {
64
+ return `Removed attribute "${trimValue(attributeName)}"`;
65
+ }
66
+
67
+ /**
68
+ * Describes a modification of an existing attribute, which covers renaming the attribute, changing
69
+ * its value, or both at once. `oldValue` is `undefined` if the previous value is unknown.
70
+ */
71
+ function describeAttributeChange(
72
+ oldAttributeName: string,
73
+ oldValue: string|undefined,
74
+ newAttributeName: string,
75
+ newValue: string,
76
+ ): string {
77
+ if (oldAttributeName !== newAttributeName) {
78
+ if (oldValue !== undefined && oldValue !== newValue) {
79
+ return `Renamed attribute "${trimValue(oldAttributeName)}"="${trimValue(oldValue)}" to "${
80
+ trimValue(newAttributeName)}"="${trimValue(newValue)}"`;
81
+ }
82
+ return `Renamed attribute "${trimValue(oldAttributeName)}" to "${trimValue(newAttributeName)}"`;
83
+ }
84
+ if (oldValue !== undefined) {
85
+ return `Changed attribute "${trimValue(newAttributeName)}" from "${trimValue(oldValue)}" to "${
86
+ trimValue(newValue)}"`;
87
+ }
88
+ return `Changed attribute "${trimValue(newAttributeName)}" to "${trimValue(newValue)}"`;
89
+ }
90
+
91
+ export interface AttributeEdit {
92
+ /** Name of the edited attribute, or an empty string when a new attribute is added. */
93
+ attributeName: string;
94
+ /** Raw attribute text before the edit, if there was any. */
95
+ oldText: string|null;
96
+ /** Raw attribute text the user committed. Empty when the attribute is removed. */
97
+ newText: string;
98
+ }
99
+
100
+ function describeAttributeEdit({attributeName, oldText, newText}: AttributeEdit): string {
101
+ const parsedNew = parseAttributeText(newText);
102
+ const parsedOld = oldText !== null ? parseAttributeText(oldText) : undefined;
103
+
104
+ if (!newText.trim()) {
105
+ return describeRemoveAttribute(attributeName.trim());
106
+ }
107
+ if (!attributeName.trim()) {
108
+ return describeAddAttribute(parsedNew.name, parsedNew.value);
109
+ }
110
+ return describeAttributeChange(
111
+ attributeName.trim(),
112
+ parsedOld?.value,
113
+ parsedNew.name || attributeName.trim(),
114
+ parsedNew.value,
115
+ );
116
+ }
117
+
118
+ /**
119
+ * These strings are for the consumption by AI agents, they don't need to be translated.
120
+ */
121
+ function describeTagNameEdit(oldTagName: string, newTagName: string): string {
122
+ return `Renamed tag from <${trimValue(oldTagName)}> to <${trimValue(newTagName)}>`;
123
+ }
124
+
125
+ function describeTextNodeEdit(oldText: string, newText: string): string {
126
+ return `Changed text from "${trimValue(oldText)}" to "${trimValue(newText)}"`;
127
+ }
128
+
129
+ function describeNodeRemoval(tagName: string): string {
130
+ return `Removed node <${trimValue(tagName)}>`;
131
+ }
132
+
133
+ function describeHTMLEdit(oldValue?: string, newValue?: string): string {
134
+ if (oldValue !== undefined && newValue !== undefined) {
135
+ return `Changed HTML from "${trimValue(oldValue)}" to "${trimValue(newValue)}"`;
136
+ }
137
+ if (newValue !== undefined) {
138
+ return `Changed HTML to "${trimValue(newValue)}"`;
139
+ }
140
+ return 'Edited HTML';
141
+ }
142
+
143
+ function describeVisibilityToggle(tagName: string, hidden: boolean): string {
144
+ return hidden ? `Hid element <${trimValue(tagName)}>` : `Unhid element <${trimValue(tagName)}>`;
145
+ }
146
+
147
+ function describeNodeDuplication(tagName: string): string {
148
+ return `Duplicated node <${trimValue(tagName)}>`;
149
+ }
150
+
151
+ function describeNodeMove(tagName: string, directionUp: boolean): string {
152
+ return `Moved node <${trimValue(tagName)}> ${directionUp ? 'up' : 'down'}`;
153
+ }
154
+
155
+ function describeNodeDrop(tagName: string): string {
156
+ return `Moved node <${trimValue(tagName)}> via drag and drop`;
157
+ }
158
+
159
+ function describeNodePaste(tagName: string, isCut: boolean): string {
160
+ return isCut ? `Pasted (moved) node <${trimValue(tagName)}>` : `Pasted node <${trimValue(tagName)}>`;
161
+ }
162
+
163
+ export function trackAttributeEdit(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
164
+ edit: AttributeEdit): void {
165
+ if (!tracker?.isTracking) {
166
+ return;
167
+ }
168
+ tracker.trackChange(describeAttributeEdit(edit), buildAnchor(node, selector));
169
+ }
170
+
171
+ export function trackTagNameEdit(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
172
+ oldTagName: string, newTagName: string): void {
173
+ if (!tracker?.isTracking) {
174
+ return;
175
+ }
176
+ tracker.trackChange(describeTagNameEdit(oldTagName, newTagName), buildAnchor(node, selector));
177
+ }
178
+
179
+ export function trackTextNodeEdit(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
180
+ oldText: string, newText: string): void {
181
+ if (!tracker?.isTracking) {
182
+ return;
183
+ }
184
+ tracker.trackChange(describeTextNodeEdit(oldText, newText), buildAnchor(node, selector));
185
+ }
186
+
187
+ export function trackNodeRemoval(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined): void {
188
+ if (!tracker?.isTracking) {
189
+ return;
190
+ }
191
+ tracker.trackChange(describeNodeRemoval(node.nodeName().toLowerCase()), buildAnchor(node, selector));
192
+ }
193
+
194
+ export function trackHTMLEdit(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
195
+ oldValue: string, newValue: string): void {
196
+ if (!tracker?.isTracking) {
197
+ return;
198
+ }
199
+ tracker.trackChange(describeHTMLEdit(oldValue, newValue), buildAnchor(node, selector));
200
+ }
201
+
202
+ export function trackVisibilityToggle(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
203
+ hidden: boolean): void {
204
+ if (!tracker?.isTracking) {
205
+ return;
206
+ }
207
+ tracker.trackChange(describeVisibilityToggle(node.nodeName().toLowerCase(), hidden), buildAnchor(node, selector));
208
+ }
209
+
210
+ export function trackNodeDuplication(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined): void {
211
+ if (!tracker?.isTracking) {
212
+ return;
213
+ }
214
+ tracker.trackChange(describeNodeDuplication(node.nodeName().toLowerCase()), buildAnchor(node, selector));
215
+ }
216
+
217
+ export function trackNodeMove(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
218
+ directionUp: boolean): void {
219
+ if (!tracker?.isTracking) {
220
+ return;
221
+ }
222
+ tracker.trackChange(describeNodeMove(node.nodeName().toLowerCase(), directionUp), buildAnchor(node, selector));
223
+ }
224
+
225
+ export function trackNodeDrop(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined): void {
226
+ if (!tracker?.isTracking) {
227
+ return;
228
+ }
229
+ tracker.trackChange(describeNodeDrop(node.nodeName().toLowerCase()), buildAnchor(node, selector));
230
+ }
231
+
232
+ export function trackNodePaste(tracker: Tracker, node: SDK.DOMModel.DOMNode, selector: string|undefined,
233
+ isCut: boolean): void {
234
+ if (!tracker?.isTracking) {
235
+ return;
236
+ }
237
+ tracker.trackChange(describeNodePaste(node.nodeName().toLowerCase(), isCut), buildAnchor(node, selector));
238
+ }
@@ -2,8 +2,10 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import * as DOMChanges from './DOMChanges.js';
5
6
  import * as ElementUpdateRecord from './ElementUpdateRecord.js';
6
7
 
7
8
  export {
9
+ DOMChanges,
8
10
  ElementUpdateRecord,
9
11
  };
@@ -2,6 +2,7 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import * as Platform from '../../core/platform/platform.js';
5
6
  import * as Marked from '../../third_party/marked/marked.js';
6
7
 
7
8
  /**
@@ -57,8 +58,7 @@ export interface IssueDescription {
57
58
 
58
59
  export async function getFileContent(url: URL): Promise<string> {
59
60
  try {
60
- const response = await fetch(url.toString());
61
- return await response.text();
61
+ return await Platform.HostRuntime.HOST_RUNTIME.loadTextFile(url);
62
62
  } catch {
63
63
  throw new Error(
64
64
  `Markdown file ${url.toString()} not found. Make sure it is correctly listed in the relevant BUILD.gn files.`);
@@ -42,8 +42,7 @@ class InjectedScript {
42
42
  static async get(): Promise<string> {
43
43
  if (!this.#injectedScript) {
44
44
  const url = new URL('./web-vitals-injected/web-vitals-injected.generated.js', import.meta.url);
45
- const result = await fetch(url);
46
- this.#injectedScript = await result.text();
45
+ this.#injectedScript = await Platform.HostRuntime.HOST_RUNTIME.loadTextFile(url);
47
46
  }
48
47
  return this.#injectedScript;
49
48
  }
@@ -232,12 +232,6 @@ export class ModelUpdateEvent extends Event {
232
232
  }
233
233
  }
234
234
 
235
- declare global {
236
- interface HTMLElementEventMap {
237
- [ModelUpdateEvent.eventName]: ModelUpdateEvent;
238
- }
239
- }
240
-
241
235
  export function isModelUpdateDataComplete(eventData: ModelUpdateEventData): eventData is ModelUpdateEventComplete {
242
236
  return eventData.type === ModelUpdateType.COMPLETE;
243
237
  }
@@ -19,7 +19,10 @@ const enum Status {
19
19
 
20
20
  export class TraceParseProgressEvent extends Event {
21
21
  static readonly eventName = 'traceparseprogress';
22
- constructor(public data: Model.TraceParseEventProgressData, init: EventInit = {bubbles: true}) {
22
+ constructor(
23
+ public data: Model.TraceParseEventProgressData,
24
+ init: ConstructorParameters<typeof Event>[1] = {bubbles: true},
25
+ ) {
23
26
  super(TraceParseProgressEvent.eventName, init);
24
27
  }
25
28
  }
@@ -44,12 +47,6 @@ function calculateProgress(value: number, phase: ProgressPhase): number {
44
47
  return value * phase;
45
48
  }
46
49
 
47
- declare global {
48
- interface HTMLElementEventMap {
49
- [TraceParseProgressEvent.eventName]: TraceParseProgressEvent;
50
- }
51
- }
52
-
53
50
  export class TraceProcessor extends EventTarget {
54
51
  // We force the Meta handler to be enabled, so the TraceHandlers type here is
55
52
  // the model handlers the user passes in and the Meta handler.
@@ -155,7 +155,8 @@ export function traceWindowFromOverlay(overlay: Types.Overlays.Overlay): Types.T
155
155
  switch (overlay.type) {
156
156
  case 'ENTRY_LABEL':
157
157
  case 'ENTRY_OUTLINE':
158
- case 'ENTRY_SELECTED': {
158
+ case 'ENTRY_SELECTED':
159
+ case 'COMMENT_PIN': {
159
160
  return traceWindowFromEvent(overlay.entry);
160
161
  }
161
162
 
@@ -131,8 +131,19 @@ export interface BottomInfoBar {
131
131
  };
132
132
  }
133
133
 
134
+ /**
135
+ * Represents an active comment pin anchored to a flame chart entry.
136
+ * Links to a CommentThread by ID in CommentManager, rendering a pin icon and
137
+ * outline highlight over the trace event.
138
+ */
139
+ export interface CommentPin {
140
+ type: 'COMMENT_PIN';
141
+ entry: OverlayEntry;
142
+ commentThreadId: string;
143
+ }
144
+
134
145
  /**
135
146
  * All supported overlay types.
136
147
  */
137
148
  export type Overlay = EntrySelected|EntryOutline|TimeRangeLabel|EntryLabel|EntriesLink|TimespanBreakdown|
138
- TimestampMarker|CandyStripedTimeRange|TimingsMarker|BottomInfoBar;
149
+ TimestampMarker|CandyStripedTimeRange|TimingsMarker|BottomInfoBar|CommentPin;
@@ -205,7 +205,28 @@ export function injectedScript(ariaLiveApi: string, jsTriggeredApi: string): voi
205
205
  let lastRecordedPoliteness: string|null = null;
206
206
  let lastRecordedTime = 0;
207
207
 
208
- // Emits an announcement payload for an active live region node via the CDP binding.
208
+ const pendingLiveNodes = new Set<Element>();
209
+ let scheduledFlushId: number|null = null;
210
+
211
+ function scheduleFlush(): void {
212
+ if (scheduledFlushId !== null) {
213
+ return;
214
+ }
215
+ scheduledFlushId = window.requestAnimationFrame(() => {
216
+ scheduledFlushId = null;
217
+ flushPendingNodes();
218
+ });
219
+ }
220
+
221
+ function flushPendingNodes(): void {
222
+ const nodes = Array.from(pendingLiveNodes);
223
+ pendingLiveNodes.clear();
224
+ for (let i = 0; i < nodes.length; i++) {
225
+ processLiveNode(nodes[i]);
226
+ }
227
+ }
228
+
229
+ // Queues an active live region node for deferred visibility checking and emission.
209
230
  function recordLiveNode(node: Node|null|undefined): void {
210
231
  if (!node || node.nodeType !== Node.ELEMENT_NODE) {
211
232
  return;
@@ -218,6 +239,22 @@ export function injectedScript(ariaLiveApi: string, jsTriggeredApi: string): voi
218
239
  if (element.getAttribute('aria-hidden') === 'true') {
219
240
  return;
220
241
  }
242
+ pendingLiveNodes.add(element);
243
+ scheduleFlush();
244
+ }
245
+
246
+ // Emits an announcement payload for an active live region node via the CDP binding after layout.
247
+ function processLiveNode(element: Element): void {
248
+ if ('isConnected' in element && !element.isConnected) {
249
+ return;
250
+ }
251
+ const politeness = derivePoliteness(element);
252
+ if (politeness === 'off') {
253
+ return;
254
+ }
255
+ if (element.getAttribute('aria-hidden') === 'true') {
256
+ return;
257
+ }
221
258
  if (!checkVisibility(element)) {
222
259
  return;
223
260
  }
@@ -293,18 +330,14 @@ export function injectedScript(ariaLiveApi: string, jsTriggeredApi: string): voi
293
330
  for (const node of mutation.addedNodes) {
294
331
  if (node.nodeType === Node.ELEMENT_NODE) {
295
332
  const el = node as Element;
296
- if (el.shadowRoot) {
297
- observeSubtree(el.shadowRoot);
298
- scanAndObserveShadowRoots(el.shadowRoot);
299
- }
300
333
  scanAndObserveShadowRoots(el);
301
334
 
302
- if (el.matches && el.matches(selector)) {
335
+ if (el.matches(selector)) {
303
336
  recordLiveNode(el);
304
337
  }
305
- const children = el.querySelectorAll ? el.querySelectorAll(selector) : [];
306
- for (const child of children) {
307
- recordLiveNode(child);
338
+ const children = el.querySelectorAll(selector);
339
+ for (let i = 0; i < children.length; i++) {
340
+ recordLiveNode(children[i]);
308
341
  }
309
342
  }
310
343
  }
@@ -348,20 +381,29 @@ export function injectedScript(ariaLiveApi: string, jsTriggeredApi: string): voi
348
381
  }
349
382
  }
350
383
 
351
- function scanAndObserveShadowRoots(node: Node|null|undefined): void {
352
- if (!node) {
384
+ function scanAndObserveShadowRoots(root: Element|ShadowRoot|null|undefined): void {
385
+ if (!root) {
353
386
  return;
354
387
  }
355
- if (node.nodeType === Node.ELEMENT_NODE) {
356
- const el = node as Element;
357
- if (el.shadowRoot) {
388
+ const queue: Array<Element|ShadowRoot> = [root];
389
+ while (queue.length > 0) {
390
+ const current = queue.pop();
391
+ if (!current) {
392
+ continue;
393
+ }
394
+ const el = current as Element;
395
+ if (el.shadowRoot && !observedRoots.has(el.shadowRoot)) {
358
396
  observeSubtree(el.shadowRoot);
359
- scanAndObserveShadowRoots(el.shadowRoot);
397
+ queue.push(el.shadowRoot);
398
+ }
399
+ const descendants = current.querySelectorAll('*');
400
+ for (let i = 0; i < descendants.length; i++) {
401
+ const descendant = descendants[i];
402
+ if (descendant.shadowRoot && !observedRoots.has(descendant.shadowRoot)) {
403
+ observeSubtree(descendant.shadowRoot);
404
+ queue.push(descendant.shadowRoot);
405
+ }
360
406
  }
361
- }
362
- const children = (node as Element).children || [];
363
- for (let i = 0; i < children.length; i++) {
364
- scanAndObserveShadowRoots(children[i]);
365
407
  }
366
408
  }
367
409
 
@@ -380,6 +422,7 @@ export function injectedScript(ariaLiveApi: string, jsTriggeredApi: string): voi
380
422
  const shadow = origAttachShadow.apply(this, [init]);
381
423
  if (init && init.mode === 'open') {
382
424
  observeSubtree(shadow);
425
+ scanAndObserveShadowRoots(shadow);
383
426
  }
384
427
  return shadow;
385
428
  };
@@ -392,6 +435,11 @@ export function injectedScript(ariaLiveApi: string, jsTriggeredApi: string): voi
392
435
  // Registers cleanup function invoked during recording teardown.
393
436
  window.__announcementsRecorderBinding_cleanup = function(): void {
394
437
  observer.disconnect();
438
+ if (scheduledFlushId !== null) {
439
+ window.cancelAnimationFrame(scheduledFlushId);
440
+ scheduledFlushId = null;
441
+ }
442
+ pendingLiveNodes.clear();
395
443
  if (originalElementAriaNotify) {
396
444
  try {
397
445
  (Element.prototype as unknown as Record<string, unknown>)['ariaNotify'] = originalElementAriaNotify;
@@ -337,7 +337,9 @@ function getMarkdownRenderer(conversation?: AiAssistanceModel.AiConversation.AiC
337
337
  return new AccessibilityAgentMarkdownRenderer(mainDocumentURL);
338
338
  }
339
339
 
340
- return new MarkdownRendererWithCodeBlock();
340
+ return new MarkdownRendererWithCodeBlock({
341
+ getEstablishedOrigin: () => conversation?.origin,
342
+ });
341
343
  }
342
344
 
343
345
  interface ToolbarViewInput {
@@ -4,6 +4,7 @@
4
4
 
5
5
  import * as Common from '../../../core/common/common.js';
6
6
  import * as Platform from '../../../core/platform/platform.js';
7
+ import type * as SDK from '../../../core/sdk/sdk.js';
7
8
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
8
9
  import * as Logs from '../../../models/logs/logs.js';
9
10
  import type * as Marked from '../../../third_party/marked/marked.js';
@@ -12,7 +13,18 @@ import * as Lit from '../../../ui/lit/lit.js';
12
13
 
13
14
  const {html} = Lit;
14
15
 
16
+ export interface MarkdownRendererWithCodeBlockOptions {
17
+ /**
18
+ * Retrieves the established origin locked for the active conversation.
19
+ * Required to authorize #file-<id> links and prevent cross-origin file leakage.
20
+ * When omitted or unauthorized, file links safely render as plain inert text.
21
+ */
22
+ getEstablishedOrigin?: () => SDK.SecurityOrigin.SecurityOrigin | undefined;
23
+ }
24
+
15
25
  /**
26
+ * Markdown renderer for AI assistance conversations.
27
+ *
16
28
  * The model returns multiline code blocks in an erroneous way with the language being in new line.
17
29
  * This renderer takes that into account and correctly updates the parsed multiline token with the language
18
30
  * correctly identified and stripped from the content.
@@ -23,8 +35,13 @@ const {html} = Lit;
23
35
  * color: red;
24
36
  * }
25
37
  * ```
26
- **/
38
+ *
39
+ * Also handles linkifying DevTools internal resource links (#req- and origin-locked #file-).
40
+ */
27
41
  export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.MarkdownInsightRenderer {
42
+ constructor(private readonly options: MarkdownRendererWithCodeBlockOptions = {}) {
43
+ super();
44
+ }
28
45
  #revealableLink(revealable: unknown, label: string): Lit.LitTemplate {
29
46
  return html`<devtools-link @click=${(e: Event) => {
30
47
  e.preventDefault();
@@ -48,13 +65,16 @@ export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.Mar
48
65
  return html`${fallbackText}`;
49
66
  }
50
67
  if (href.startsWith('#file-')) {
51
- const file = AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.getUISourceCodes().find(
52
- file => AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.uiSourceCodeId.get(file) ===
53
- Number(href.substring(6)));
68
+ const fileId = Number(href.substring(6));
69
+ const file = AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.getSourceById(
70
+ fileId,
71
+ this.options.getEstablishedOrigin?.(),
72
+ );
54
73
 
55
74
  if (file) {
56
75
  return this.#revealableLink(file, file.name());
57
76
  }
77
+ // Fall back to plain text to prevent rendering unauthorized or unverified links.
58
78
  return html`${fallbackText}`;
59
79
  }
60
80
  return null;
@@ -78,7 +98,7 @@ export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.Mar
78
98
 
79
99
  if (token.type === 'codespan') {
80
100
  // LLM likes outputting the link inside a codespan block.
81
- // Remove the codespan and render the link directly
101
+ // Remove the codespan and render the link directly.
82
102
  const matches = token.text.match(/^\[(.*)\]\((.+)\)$/);
83
103
  if (matches?.[2]) {
84
104
  const link = this.#renderLink(
@@ -298,7 +298,7 @@ const renderDataGrid = (input: IndexedDBDataViewInput): LitTemplate => {
298
298
  ${input.isIndex ? html`<th id="primary-key" weight="2">${renderKeyColumnHeader(i18nString(UIStrings.primaryKey), input.objectStore.keyPath)}</th>` : nothing}
299
299
  <th id="value" weight="8">${i18nString(UIStrings.valueString)}</th>
300
300
  </tr>
301
- ${repeat(input.entries, (_entry, index) => index, (entry, index) => {
301
+ ${repeat(input.entries, entry => entry, (entry, index) => {
302
302
  return html`
303
303
  <tr ?selected=${index + input.skipCount === input.selectedRowNumber}
304
304
  class="data-grid-data-row"
@@ -20,8 +20,6 @@ export class ObjectEventListenersSidebarPane extends UI.Widget.VBox implements U
20
20
  this.contentElement.setAttribute('jslog', `${VisualLogging.section('sources.global-listeners')}`);
21
21
 
22
22
  this.eventListenersView = new EventListeners.EventListenersView.EventListenersView();
23
- this.eventListenersView.changeCallback = this.requestUpdate.bind(this);
24
- this.eventListenersView.enableDefaultTreeFocus = true;
25
23
  this.eventListenersView.show(this.element);
26
24
  this.setDefaultFocusedChild(this.eventListenersView);
27
25
  this.requestUpdate();
@@ -57,7 +55,8 @@ export class ObjectEventListenersSidebarPane extends UI.Widget.VBox implements U
57
55
  windowObjects.push(result.object);
58
56
  }
59
57
  }
60
- await this.eventListenersView.addObjects(windowObjects);
58
+ this.eventListenersView.objects = windowObjects;
59
+ await this.eventListenersView.updateComplete;
61
60
  }
62
61
 
63
62
  override wasShown(): void {