chrome-devtools-frontend 1.0.1697998 → 1.0.1698733

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 (102) hide show
  1. package/.agents/skills/version-control/SKILL.md +21 -9
  2. package/front_end/core/platform/HostRuntime.ts +12 -0
  3. package/front_end/core/platform/api/HostRuntime.ts +13 -0
  4. package/front_end/core/platform/browser/HostRuntime.ts +34 -0
  5. package/front_end/core/platform/node/HostRuntime.ts +40 -0
  6. package/front_end/core/sdk/AnimationModel.ts +12 -98
  7. package/front_end/core/sdk/CSSModel.ts +4 -2
  8. package/front_end/core/sdk/CSSPropertyParser.ts +4 -4
  9. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +4 -44
  10. package/front_end/core/sdk/Connections.ts +6 -1
  11. package/front_end/core/sdk/ConsoleModel.ts +1 -12
  12. package/front_end/core/sdk/DOMModel.ts +3 -51
  13. package/front_end/core/sdk/DebuggerModel.ts +9 -8
  14. package/front_end/core/sdk/NetworkRequest.ts +1 -1
  15. package/front_end/core/sdk/PageFunctions.ts +187 -0
  16. package/front_end/core/sdk/Resource.ts +1 -1
  17. package/front_end/core/sdk/RuntimeModel.ts +1 -20
  18. package/front_end/core/sdk/Script.ts +21 -0
  19. package/front_end/core/sdk/SourceMap.ts +15 -2
  20. package/front_end/core/sdk/SourceMapCache.ts +13 -10
  21. package/front_end/core/sdk/SourceMapManager.ts +11 -9
  22. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +59 -17
  23. package/front_end/core/sdk/SourceMapScopesInfo.ts +8 -2
  24. package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +635 -0
  25. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +26 -12
  26. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotLoader.ts +16 -2
  27. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +4 -4
  28. package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker.ts +2 -0
  29. package/front_end/entrypoints/shell/shell.ts +1 -1
  30. package/front_end/models/ai_assistance/DOMHelpers.ts +95 -0
  31. package/front_end/models/ai_assistance/EvaluateAction.ts +5 -42
  32. package/front_end/models/ai_assistance/ExtensionScope.ts +2 -31
  33. package/front_end/models/ai_assistance/agents/AiAgent.ts +2 -1
  34. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +3 -4
  35. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  36. package/front_end/models/ai_assistance/contexts/DOMNodeContext.ts +10 -7
  37. package/front_end/models/ai_assistance/contexts/FileContext.ts +2 -10
  38. package/front_end/models/ai_assistance/tools/GetSourceContent.ts +3 -2
  39. package/front_end/models/ai_assistance/tools/ListSources.ts +21 -14
  40. package/front_end/models/bindings/CompilerScriptMapping.ts +3 -10
  41. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +48 -17
  42. package/front_end/models/bindings/ResourceScriptMapping.ts +2 -2
  43. package/front_end/models/bindings/StylesSourceMapping.ts +1 -1
  44. package/front_end/models/comment_manager/CD4ABridge.ts +55 -12
  45. package/front_end/models/comment_manager/CommentManager.ts +46 -91
  46. package/front_end/models/comment_manager/CommentThread.ts +145 -0
  47. package/front_end/models/comment_manager/comment_manager.ts +2 -0
  48. package/front_end/models/emulation/DeviceModeModel.ts +53 -0
  49. package/front_end/models/extensions/ExtensionAPI.ts +8 -6
  50. package/front_end/models/extensions/ExtensionEndpoint.ts +11 -8
  51. package/front_end/models/extensions/HostUrlPattern.ts +4 -3
  52. package/front_end/models/extensions/LanguageExtensionEndpoint.ts +3 -2
  53. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +2 -1
  54. package/front_end/models/formatter/FormatterWorkerPool.ts +4 -6
  55. package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +55 -0
  56. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +18 -14
  57. package/front_end/models/issues_manager/IssuesManager.ts +5 -0
  58. package/front_end/models/issues_manager/WebInstallIssue.ts +63 -0
  59. package/front_end/models/issues_manager/descriptions/webInstallManifestMissingId.md +3 -0
  60. package/front_end/models/issues_manager/descriptions/webInstallManifestMissingNameOrShortName.md +3 -0
  61. package/front_end/models/issues_manager/descriptions/webInstallManifestParsingOrNetworkError.md +3 -0
  62. package/front_end/models/issues_manager/descriptions/webInstallNoManifest.md +3 -0
  63. package/front_end/models/issues_manager/descriptions/webInstallStartUrlInvalid.md +3 -0
  64. package/front_end/models/issues_manager/issues_manager.ts +2 -0
  65. package/front_end/models/workspace/UISourceCode.ts +20 -0
  66. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +4 -6
  67. package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +4 -10
  68. package/front_end/{ui/comments → panels/common}/CommentThreadWidget.ts +4 -4
  69. package/front_end/{ui/comments → panels/common}/CommentsOverlayWidget.ts +39 -62
  70. package/front_end/{ui/comments → panels/common}/CommentsStatusBarPill.ts +5 -5
  71. package/front_end/panels/common/ExtensionServer.ts +2 -1
  72. package/front_end/{ui/comments → panels/common}/commentsOverlay.css +2 -1
  73. package/front_end/{ui/comments/comments-meta.ts → panels/common/common-meta.ts} +11 -11
  74. package/front_end/panels/common/common.ts +3 -0
  75. package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +3 -3
  76. package/front_end/panels/elements/ElementsPanel.ts +5 -1
  77. package/front_end/panels/elements/ElementsTreeElement.ts +4 -1
  78. package/front_end/panels/elements/ElementsTreeOutline.ts +58 -51
  79. package/front_end/panels/elements/PropertiesWidget.ts +11 -10
  80. package/front_end/panels/elements/PropertyRenderer.ts +29 -25
  81. package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -6
  82. package/front_end/panels/elements/elementsTreeOutline.css +7 -1
  83. package/front_end/panels/emulation/DeviceModeToolbar.ts +2 -3
  84. package/front_end/panels/event_listeners/EventListenersView.ts +33 -44
  85. package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorController.ts +6 -7
  86. package/front_end/panels/linear_memory_inspector/components/linearMemoryInspector.css +5 -1
  87. package/front_end/panels/linear_memory_inspector/linear_memory_inspector-meta.ts +1 -1
  88. package/front_end/panels/recorder/stepView.css +1 -0
  89. package/front_end/panels/security/SecurityPanel.ts +5 -5
  90. package/front_end/panels/security/SecurityPanelSidebar.ts +2 -11
  91. package/front_end/panels/sources/DebuggerPlugin.ts +1 -1
  92. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +11 -16
  93. package/front_end/panels/sources/sources-meta.ts +1 -1
  94. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +0 -3
  95. package/front_end/third_party/chromium/README.chromium +1 -1
  96. package/front_end/ui/comments/CommentOverlayManager.ts +28 -110
  97. package/front_end/ui/comments/comments.ts +0 -6
  98. package/front_end/ui/legacy/Treeoutline.ts +4 -0
  99. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +14 -7
  100. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  101. package/package.json +1 -1
  102. /package/front_end/{ui/comments → panels/common}/commentThreadWidget.css +0 -0
@@ -4,9 +4,7 @@
4
4
 
5
5
  import * as Host from '../../../core/host/host.js';
6
6
  import * as i18n from '../../../core/i18n/i18n.js';
7
- import type * as SDK from '../../../core/sdk/sdk.js';
8
7
  import * as Workspace from '../../workspace/workspace.js';
9
- import {FileContext} from '../contexts/FileContext.js';
10
8
 
11
9
  import {
12
10
  type BaseToolCapability,
@@ -50,23 +48,30 @@ export class ListSourcesTool implements
50
48
  }
51
49
 
52
50
  static getUISourceCodes(
53
- establishedOrigin: SDK.SecurityOrigin.SecurityOrigin,
51
+ originLock: OriginLockState,
54
52
  // eslint-disable-next-line @devtools/no-instance-of-migrated-singletons
55
53
  workspace: Workspace.Workspace.WorkspaceImpl = Workspace.Workspace.WorkspaceImpl.instance(),
56
54
  ): Workspace.UISourceCode.UISourceCode[] {
57
- if (establishedOrigin.isOpaque()) {
55
+ if (originLock.status !== 'ESTABLISHED_ORIGIN' || originLock.origin.isOpaque()) {
58
56
  return [];
59
57
  }
60
58
 
61
- const projects =
62
- workspace.projects().filter(project => project.type() === Workspace.Workspace.projectTypes.Network);
63
59
  const uiSourceCodes = new Map<string, Workspace.UISourceCode.UISourceCode>();
64
60
 
65
- for (const project of projects) {
61
+ for (const project of workspace.projectsForType(Workspace.Workspace.projectTypes.Network)) {
62
+ const projectOrigin = project.securityOrigin?.();
63
+ if (projectOrigin && !isOriginAllowedByLock(originLock, projectOrigin)) {
64
+ continue;
65
+ }
66
+
66
67
  for (const uiSourceCode of project.uiSourceCodes()) {
67
68
  if (uiSourceCode.isIgnoreListed()) {
68
69
  continue;
69
70
  }
71
+ if (!projectOrigin && !isOriginAllowedByLock(originLock, uiSourceCode.securityOrigin())) {
72
+ continue;
73
+ }
74
+
70
75
  const url = uiSourceCode.url();
71
76
  if (!uiSourceCodes.get(url) || uiSourceCode.contentType().isFromSourceMap()) {
72
77
  uiSourceCodes.set(url, uiSourceCode);
@@ -77,18 +82,19 @@ export class ListSourcesTool implements
77
82
  }
78
83
  }
79
84
 
80
- const originLock: OriginLockState = {status: 'ESTABLISHED_ORIGIN', origin: establishedOrigin};
81
- return [...uiSourceCodes.values()].filter(
82
- file => isOriginAllowedByLock(originLock, FileContext.originForUISourceCode(file)));
85
+ return Array.from(uiSourceCodes.values());
83
86
  }
84
87
 
85
88
  static getSourceById(
86
89
  id: number,
87
- establishedOrigin: SDK.SecurityOrigin.SecurityOrigin,
90
+ originLock: OriginLockState,
88
91
  // eslint-disable-next-line @devtools/no-instance-of-migrated-singletons
89
92
  workspace: Workspace.Workspace.WorkspaceImpl = Workspace.Workspace.WorkspaceImpl.instance(),
90
93
  ): Workspace.UISourceCode.UISourceCode|undefined {
91
- return ListSourcesTool.getUISourceCodes(establishedOrigin, workspace)
94
+ if (!Number.isInteger(id) || id <= 0) {
95
+ return undefined;
96
+ }
97
+ return ListSourcesTool.getUISourceCodes(originLock, workspace)
92
98
  .find(file => ListSourcesTool.uiSourceCodeId.get(file) === id);
93
99
  }
94
100
 
@@ -114,12 +120,13 @@ export class ListSourcesTool implements
114
120
  _params: Record<string, never>,
115
121
  context: BaseToolCapability&OriginLockCapability,
116
122
  ): Promise<DataHandlerResult<{files: SourceSummary[]}>> {
117
- const originResult = resolveOriginFromLock(context.getOriginLock());
123
+ const originLock = context.getOriginLock();
124
+ const originResult = resolveOriginFromLock(originLock);
118
125
  if ('error' in originResult) {
119
126
  return originResult;
120
127
  }
121
128
 
122
- const files = ListSourcesTool.getUISourceCodes(originResult.origin);
129
+ const files = ListSourcesTool.getUISourceCodes(originLock);
123
130
 
124
131
  return {
125
132
  result: {
@@ -458,16 +458,9 @@ export class CompilerScriptMapping implements DebuggerSourceMapping {
458
458
  this.removeStubUISourceCode(script);
459
459
 
460
460
  const target = script.target();
461
- const embedderName = script.embedderName();
462
- let securityOrigin: SDK.SecurityOrigin.SecurityOrigin|undefined;
463
- if (embedderName) {
464
- const extractedOrigin = Common.ParsedURL.ParsedURL.extractOrigin(embedderName);
465
- if (extractedOrigin && extractedOrigin !== 'null') {
466
- securityOrigin = SDK.SecurityOrigin.SecurityOrigin.create(extractedOrigin);
467
- }
468
- }
469
- const parsedOrigin = securityOrigin ? `:${securityOrigin.siteId()}` : '';
470
- const projectId = `jsSourceMaps:${script.isContentScript() ? 'extensions' : ''}:${target.id()}${parsedOrigin}`;
461
+ const securityOrigin = script.securityOrigin();
462
+ const originPart = securityOrigin.isOpaque() ? '' : `:${securityOrigin.siteId()}`;
463
+ const projectId = `jsSourceMaps:${script.isContentScript() ? 'extensions' : ''}:${target.id()}${originPart}`;
471
464
  let project = this.#projects.get(projectId);
472
465
  if (!project) {
473
466
  const projectType = script.isContentScript() ? Workspace.Workspace.projectTypes.ContentScripts :
@@ -593,11 +593,11 @@ export class DebuggerLanguagePluginManager implements
593
593
  return {rawModuleId, plugin: null};
594
594
  }
595
595
 
596
- uiSourceCodeForURL(debuggerModel: SDK.DebuggerModel.DebuggerModel, url: Platform.DevToolsPath.UrlString):
597
- Workspace.UISourceCode.UISourceCode|null {
596
+ uiSourceCodeForURL(debuggerModel: SDK.DebuggerModel.DebuggerModel, url: Platform.DevToolsPath.UrlString,
597
+ script?: SDK.Script.Script): Workspace.UISourceCode.UISourceCode|null {
598
598
  const modelData = this.#debuggerModelToData.get(debuggerModel);
599
599
  if (modelData) {
600
- return modelData.getProject().uiSourceCodeForURL(url);
600
+ return modelData.uiSourceCodeForURL(url, script);
601
601
  }
602
602
  return null;
603
603
  }
@@ -625,7 +625,7 @@ export class DebuggerLanguagePluginManager implements
625
625
  const sourceLocations = await plugin.rawLocationToSourceLocation(pluginLocation);
626
626
  for (const sourceLocation of sourceLocations) {
627
627
  const uiSourceCode = this.uiSourceCodeForURL(
628
- script.debuggerModel, sourceLocation.sourceFileURL as Platform.DevToolsPath.UrlString);
628
+ script.debuggerModel, sourceLocation.sourceFileURL as Platform.DevToolsPath.UrlString, script);
629
629
  if (!uiSourceCode) {
630
630
  continue;
631
631
  }
@@ -1097,23 +1097,41 @@ export class DebuggerLanguagePluginManager implements
1097
1097
  }
1098
1098
 
1099
1099
  class ModelData {
1100
- project: ContentProviderBasedProject;
1100
+ readonly #debuggerModel: SDK.DebuggerModel.DebuggerModel;
1101
+ readonly #workspace: Workspace.Workspace.WorkspaceImpl;
1102
+ readonly #projects = new Map<string, ContentProviderBasedProject>();
1101
1103
  readonly uiSourceCodeToScripts: Map<Workspace.UISourceCode.UISourceCode, SDK.Script.Script[]>;
1102
1104
  constructor(debuggerModel: SDK.DebuggerModel.DebuggerModel, workspace: Workspace.Workspace.WorkspaceImpl) {
1103
- this.project =
1104
- new ContentProviderBasedProject(workspace, 'language_plugins::' + debuggerModel.target().id(),
1105
- Workspace.Workspace.projectTypes.Network, '', false /* isServiceProject */);
1106
- NetworkProject.setTargetForProject(this.project, debuggerModel.target());
1107
-
1105
+ this.#debuggerModel = debuggerModel;
1106
+ this.#workspace = workspace;
1108
1107
  this.uiSourceCodeToScripts = new Map();
1109
1108
  }
1110
1109
 
1110
+ #projectIdForScript(script: SDK.Script.Script): string {
1111
+ const securityOrigin = script.securityOrigin();
1112
+ const originPart = securityOrigin.isOpaque() ? '' : `:${securityOrigin.siteId()}`;
1113
+ return `language_plugins::${this.#debuggerModel.target().id()}${originPart}`;
1114
+ }
1115
+
1116
+ #projectForScript(script: SDK.Script.Script): ContentProviderBasedProject {
1117
+ const projectId = this.#projectIdForScript(script);
1118
+ let project = this.#projects.get(projectId);
1119
+ if (!project) {
1120
+ project = new ContentProviderBasedProject(this.#workspace, projectId, Workspace.Workspace.projectTypes.Network,
1121
+ '', false /* isServiceProject */, script.securityOrigin());
1122
+ NetworkProject.setTargetForProject(project, this.#debuggerModel.target());
1123
+ this.#projects.set(projectId, project);
1124
+ }
1125
+ return project;
1126
+ }
1127
+
1111
1128
  addSourceFiles(script: SDK.Script.Script, urls: Platform.DevToolsPath.UrlString[]): void {
1129
+ const project = this.#projectForScript(script);
1112
1130
  const initiator = script.createPageResourceLoadInitiator();
1113
1131
  for (const url of urls) {
1114
- let uiSourceCode = this.project.uiSourceCodeForURL(url);
1132
+ let uiSourceCode = project.uiSourceCodeForURL(url);
1115
1133
  if (!uiSourceCode) {
1116
- uiSourceCode = this.project.createUISourceCode(url, Common.ResourceType.resourceTypes.SourceMapScript);
1134
+ uiSourceCode = project.createUISourceCode(url, Common.ResourceType.resourceTypes.SourceMapScript);
1117
1135
  NetworkProject.setInitialFrameAttribution(uiSourceCode, script.frameId);
1118
1136
 
1119
1137
  // Bind the uiSourceCode to the script first before we add the
@@ -1129,7 +1147,7 @@ class ModelData {
1129
1147
  url, Common.ResourceType.resourceTypes.SourceMapScript, initiator,
1130
1148
  script.target().targetManager().getPageResourceLoader());
1131
1149
  const mimeType = Common.ResourceType.ResourceType.mimeFromURL(url) || 'text/javascript';
1132
- this.project.addUISourceCodeWithProvider(uiSourceCode, contentProvider, null, mimeType);
1150
+ project.addUISourceCodeWithProvider(uiSourceCode, contentProvider, null, mimeType);
1133
1151
  } else {
1134
1152
  // The same uiSourceCode can be provided by different scripts,
1135
1153
  // but we don't expect that to happen frequently.
@@ -1146,7 +1164,7 @@ class ModelData {
1146
1164
  scripts = scripts.filter(s => s !== script);
1147
1165
  if (scripts.length === 0) {
1148
1166
  this.uiSourceCodeToScripts.delete(uiSourceCode);
1149
- this.project.removeUISourceCode(uiSourceCode.url());
1167
+ uiSourceCode.project().removeUISourceCode(uiSourceCode.url());
1150
1168
  } else {
1151
1169
  this.uiSourceCodeToScripts.set(uiSourceCode, scripts);
1152
1170
  }
@@ -1154,11 +1172,24 @@ class ModelData {
1154
1172
  }
1155
1173
 
1156
1174
  dispose(): void {
1157
- this.project.dispose();
1175
+ for (const project of this.#projects.values()) {
1176
+ project.dispose();
1177
+ }
1178
+ this.#projects.clear();
1158
1179
  }
1159
1180
 
1160
- getProject(): ContentProviderBasedProject {
1161
- return this.project;
1181
+ uiSourceCodeForURL(url: Platform.DevToolsPath.UrlString,
1182
+ script?: SDK.Script.Script): Workspace.UISourceCode.UISourceCode|null {
1183
+ if (script) {
1184
+ return this.#projects.get(this.#projectIdForScript(script))?.uiSourceCodeForURL(url) ?? null;
1185
+ }
1186
+ for (const project of this.#projects.values()) {
1187
+ const uiSourceCode = project.uiSourceCodeForURL(url);
1188
+ if (uiSourceCode) {
1189
+ return uiSourceCode;
1190
+ }
1191
+ }
1192
+ return null;
1162
1193
  }
1163
1194
  }
1164
1195
 
@@ -312,11 +312,11 @@ export class ResourceScriptFile {
312
312
  this.script = this.uiSourceCode.contentType().isScript() ? script : null;
313
313
  }
314
314
 
315
- addSourceMapURL(sourceMapURL: Platform.DevToolsPath.UrlString): void {
315
+ addSourceMapURL(sourceMapURL: Platform.DevToolsPath.UrlString, provenance: SDK.SourceMap.SourceMapProvenance): void {
316
316
  if (!this.script) {
317
317
  return;
318
318
  }
319
- this.script.debuggerModel.setSourceMapURL(this.script, sourceMapURL);
319
+ this.script.debuggerModel.setSourceMapURL(this.script, sourceMapURL, provenance);
320
320
  }
321
321
 
322
322
  addDebugInfoURL(debugInfoURL: Platform.DevToolsPath.UrlString): void {
@@ -310,7 +310,7 @@ export class StyleFile implements TextUtils.ContentProvider.ContentProvider {
310
310
  const sourceMapManager = this.#cssModel.sourceMapManager();
311
311
  this.headers.forEach(header => {
312
312
  sourceMapManager.detachSourceMap(header);
313
- sourceMapManager.attachSourceMap(header, sourceUrl, sourceMapUrl);
313
+ sourceMapManager.attachSourceMap(header, sourceUrl, sourceMapUrl, SDK.SourceMap.SourceMapProvenance.USER);
314
314
  });
315
315
  }
316
316
  }
@@ -14,13 +14,12 @@ export interface CommentThread {
14
14
  id: string;
15
15
  text: string;
16
16
  networkRequestId?: string;
17
- backendNodeId?: number;
18
- editor?: CommentManager.EditorAnchorSignature;
17
+ node?: CommentManager.DOMNodeAnchorSignature;
19
18
  }
20
19
 
21
20
  export interface RevealTarget {
22
21
  networkRequestId?: string;
23
- backendNodeId?: number;
22
+ node?: CommentManager.DOMNodeAnchorSignature;
24
23
  }
25
24
 
26
25
  export const enum Events {
@@ -69,21 +68,65 @@ export class CD4ABridge extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
69
68
  Common.EventTarget.removeEventListeners(this.#eventListeners);
70
69
  }
71
70
 
71
+ #getDOMNode(nodeSignature: CommentManager.DOMNodeAnchorSignature): SDK.DOMModel.DOMNode|undefined {
72
+ if (!this.#targetManager) {
73
+ return undefined;
74
+ }
75
+ const target = this.#targetManager.targetById(nodeSignature.targetId) ?? this.#targetManager.primaryPageTarget();
76
+ const domModel = target?.model(SDK.DOMModel.DOMModel);
77
+ if (!domModel) {
78
+ return undefined;
79
+ }
80
+ for (const node of domModel.idToDOMNode.values()) {
81
+ if (node.backendNodeId() === nodeSignature.backendNodeId) {
82
+ return node;
83
+ }
84
+ }
85
+ return undefined;
86
+ }
87
+
88
+ #formatCommentText(thread: CommentManager.CommentThread): string {
89
+ const rawText = thread.comments[0]?.text ?? '';
90
+ const details: string[] = [];
91
+
92
+ if (thread.anchor.textSignature) {
93
+ details.push(`- DevTools element: ${thread.anchor.textSignature}`);
94
+ }
95
+
96
+ if (thread.anchor.node) {
97
+ const domNode = this.#getDOMNode(thread.anchor.node);
98
+ const simpleSelector = domNode?.simpleSelector();
99
+ if (simpleSelector) {
100
+ details.push(`- DOM node selector: ${simpleSelector}`);
101
+ }
102
+ }
103
+
104
+ if (thread.anchor.editor) {
105
+ const editorInfo = thread.anchor.editor.filePath ?
106
+ `${thread.anchor.editor.filePath}:${thread.anchor.editor.lineNumber}` :
107
+ `line ${thread.anchor.editor.lineNumber}`;
108
+ details.push(`- Editor: ${editorInfo}`);
109
+ }
110
+
111
+ if (details.length === 0) {
112
+ return rawText;
113
+ }
114
+
115
+ return rawText ? `${rawText}\n\n${details.join('\n')}` : details.join('\n');
116
+ }
117
+
72
118
  getCommentThreads(): CommentThread[] {
73
119
  const threads = this.#commentManager.takeComments();
74
120
  return threads.map(thread => {
75
121
  const threadPayload: CommentThread = {
76
122
  id: thread.id,
77
- text: thread.comments[0]?.text ?? '',
123
+ text: this.#formatCommentText(thread),
78
124
  };
79
125
  if (thread.anchor.networkRequestId) {
80
126
  threadPayload.networkRequestId = thread.anchor.networkRequestId;
81
127
  }
82
128
  if (thread.anchor.node) {
83
- threadPayload.backendNodeId = thread.anchor.node.backendNodeId;
84
- }
85
- if (thread.anchor.editor) {
86
- threadPayload.editor = thread.anchor.editor;
129
+ threadPayload.node = {...thread.anchor.node};
87
130
  }
88
131
  return threadPayload;
89
132
  });
@@ -112,11 +155,11 @@ export class CD4ABridge extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
112
155
  }
113
156
  }
114
157
 
115
- if (target?.backendNodeId !== undefined && this.#targetManager) {
116
- const primaryTarget = this.#targetManager.primaryPageTarget();
117
- const domModel = primaryTarget?.model(SDK.DOMModel.DOMModel);
158
+ if (target?.node && this.#targetManager) {
159
+ const sdkTarget = this.#targetManager.targetById(target.node.targetId) ?? this.#targetManager.primaryPageTarget();
160
+ const domModel = sdkTarget?.model(SDK.DOMModel.DOMModel);
118
161
  if (domModel) {
119
- const cdpNodeId = target.backendNodeId as Protocol.DOM.BackendNodeId;
162
+ const cdpNodeId = target.node.backendNodeId as Protocol.DOM.BackendNodeId;
120
163
  const nodeMap = await domModel.pushNodesByBackendIdsToFrontend(new Set([cdpNodeId]));
121
164
  const node = nodeMap?.get(cdpNodeId);
122
165
  if (node) {
@@ -4,75 +4,30 @@
4
4
 
5
5
  import * as Common from '../../core/common/common.js';
6
6
 
7
- export interface EditorAnchorSignature {
8
- /** 1-based line number for CodeMirror text editor anchors */
9
- lineNumber: number;
10
- /** File path associated with the editor */
11
- filePath?: string;
12
- }
13
-
14
- export interface DOMNodeAnchorSignature {
15
- /** Backend NodeId for DOM nodes (`data-backend-node-id`) */
16
- backendNodeId: number;
17
- /** Target ID associated with the DOM node (`data-target-id`) */
18
- targetId: string;
19
- }
20
-
21
- export interface TimelineAnchorSignature {
22
- /** Identifier of the trace to scope comments to a specific recording */
23
- traceId: string;
24
- /** Serializable key from Trace.EventsSerializer (e.g. 'r-123', 'p-1-2-3-4', 's-5') */
25
- traceEventKey: string;
26
- /** Primary event title or category name */
27
- entryName: string;
28
- /** Event start timestamp in microseconds */
29
- startTimeMicro: number;
30
- /** Chart location */
31
- chartLocation: 'main'|'network';
32
- /** Event duration in microseconds (omitted for instant markers) */
33
- durationMicro?: number;
34
- }
35
-
36
- export interface CommentAnchorSignature {
37
- /** Visual logging tree path, e.g. "Panel: elements > Pane: styles > TreeOutline > TreeItem: color" */
38
- vePath: string;
39
- /** Normalized text content of the target node */
40
- textSignature: string;
41
- /** Text content of the parent container VE node for sibling disambiguation */
42
- parentTextSignature?: string;
43
- /** 0-indexed position among siblings sharing the same visual logging path */
44
- siblingIndex?: number;
45
- /** Optional backend RequestId for Network panel elements (`data-network-request-id`) */
46
- networkRequestId?: string;
47
- /** Optional DOM node identifiers (`data-backend-node-id`, `data-target-id`) */
48
- node?: DOMNodeAnchorSignature;
49
- /** Optional editor anchor coordinates for CodeMirror text editors */
50
- editor?: EditorAnchorSignature;
51
- /** Optional timeline flamechart anchor coordinates for Performance panel trace entries */
52
- timeline?: TimelineAnchorSignature;
53
- }
54
-
55
- export interface Comment {
56
- author: 'DEVELOPER'|'AGENT';
57
- text: string;
58
- timestamp: number;
59
- }
60
-
61
- export interface ChangeRecord {
62
- id: string;
63
- description: string;
64
- timestamp: number;
65
- }
66
-
67
- export interface CommentThread {
68
- id: string;
69
- anchor: CommentAnchorSignature;
70
- comments: Comment[];
71
- status: 'ACTIVE'|'RESOLVED';
72
- transmitted?: boolean;
73
- changes?: ChangeRecord[];
74
- index: number;
75
- }
7
+ import {
8
+ type ChangeRecord,
9
+ type Comment,
10
+ type CommentAnchorSignature,
11
+ CommentThread,
12
+ type CommentThreadOptions,
13
+ type CommentThreadStatus,
14
+ type DOMNodeAnchorSignature,
15
+ type EditorAnchorSignature,
16
+ Events as CommentThreadEvents,
17
+ type TimelineAnchorSignature,
18
+ } from './CommentThread.js';
19
+
20
+ export {
21
+ type ChangeRecord,
22
+ type Comment,
23
+ type CommentAnchorSignature,
24
+ CommentThread,
25
+ type CommentThreadOptions,
26
+ type CommentThreadStatus,
27
+ type DOMNodeAnchorSignature,
28
+ type EditorAnchorSignature,
29
+ type TimelineAnchorSignature,
30
+ };
76
31
 
77
32
  export const enum Events {
78
33
  COMMENT_THREADS_CHANGED = 'CommentThreadsChanged',
@@ -90,7 +45,15 @@ export interface EventTypes {
90
45
  export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
91
46
  readonly #commentThreads = new Map<string, CommentThread>();
92
47
  #commentMode = false;
93
- #nextId = 1;
48
+
49
+ constructor() {
50
+ super();
51
+ CommentThread.resetIndex();
52
+ }
53
+
54
+ #onThreadChanged(): void {
55
+ this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
56
+ }
94
57
 
95
58
  setCommentMode(active: boolean): void {
96
59
  if (this.#commentMode === active) {
@@ -110,25 +73,20 @@ export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventType
110
73
  author: 'DEVELOPER'|'AGENT' = 'DEVELOPER',
111
74
  changes?: ChangeRecord[],
112
75
  ): CommentThread {
113
- const index = this.#nextId++;
114
- const id = `comment-${index}`;
115
76
  const comments: Comment[] = text ? [{
116
77
  author,
117
78
  text,
118
79
  timestamp: Date.now(),
119
80
  }] :
120
81
  [];
121
- const thread: CommentThread = {
122
- id,
82
+ const thread = new CommentThread({
123
83
  anchor,
124
84
  comments,
125
- status: 'ACTIVE',
126
- transmitted: false,
127
85
  changes,
128
- index,
129
- };
86
+ });
87
+ thread.addEventListener(CommentThreadEvents.CHANGED, this.#onThreadChanged, this);
130
88
 
131
- this.#commentThreads.set(id, thread);
89
+ this.#commentThreads.set(thread.id, thread);
132
90
  this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
133
91
  return thread;
134
92
  }
@@ -144,7 +102,7 @@ export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventType
144
102
  takeComments(): CommentThread[] {
145
103
  const threads: CommentThread[] = [];
146
104
  for (const thread of this.#commentThreads.values()) {
147
- if (!thread.transmitted) {
105
+ if (thread.status === 'ACTIVE' && !thread.transmitted) {
148
106
  thread.transmitted = true;
149
107
  threads.push(thread);
150
108
  }
@@ -157,30 +115,27 @@ export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventType
157
115
  if (!thread) {
158
116
  return false;
159
117
  }
160
- if (replyText && replyText.trim().length > 0) {
161
- const comment: Comment = {
162
- author: 'AGENT',
163
- text: replyText.trim(),
164
- timestamp: Date.now(),
165
- };
166
- thread.comments.push(comment);
167
- }
168
- thread.status = 'RESOLVED';
169
- this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
118
+ thread.resolve(replyText);
170
119
  return true;
171
120
  }
172
121
 
173
122
  removeCommentThread(id: string): void {
174
- if (!this.#commentThreads.has(id)) {
123
+ const thread = this.#commentThreads.get(id);
124
+ if (!thread) {
175
125
  return;
176
126
  }
127
+ thread.removeEventListener(CommentThreadEvents.CHANGED, this.#onThreadChanged, this);
177
128
  this.#commentThreads.delete(id);
178
129
  this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
179
130
  }
180
131
 
181
132
  clear(): void {
182
133
  this.setCommentMode(false);
134
+ for (const thread of this.#commentThreads.values()) {
135
+ thread.removeEventListener(CommentThreadEvents.CHANGED, this.#onThreadChanged, this);
136
+ }
183
137
  this.#commentThreads.clear();
138
+ CommentThread.resetIndex();
184
139
  this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, []);
185
140
  }
186
141
  }