chrome-devtools-frontend 1.0.1697595 → 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 (39) hide show
  1. package/.stylelintignore +2 -0
  2. package/front_end/core/common/VersionController.ts +5 -1
  3. package/front_end/core/sdk/AnimationModel.ts +9 -1
  4. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  5. package/front_end/generated/protocol.ts +8 -2
  6. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +26 -0
  7. package/front_end/models/ai_assistance/ai_assistance.ts +4 -4
  8. package/front_end/models/ai_assistance/skills/performance.md +4 -3
  9. package/front_end/models/ai_assistance/tools/{GetFunctionCode.ts → GetTraceFunctionCode.ts} +26 -13
  10. package/front_end/models/ai_assistance/tools/{GetResourceContent.ts → GetTraceResourceContent.ts} +31 -11
  11. package/front_end/models/ai_assistance/tools/Tool.ts +2 -2
  12. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +6 -6
  13. package/front_end/models/elements/DOMChanges.ts +238 -0
  14. package/front_end/models/elements/elements.ts +2 -0
  15. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +3 -1
  16. package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +25 -5
  17. package/front_end/panels/browser_debugger/ObjectEventListenersSidebarPane.ts +2 -3
  18. package/front_end/panels/elements/ElementsTreeElement.ts +98 -20
  19. package/front_end/panels/elements/ElementsTreeOutline.ts +271 -43
  20. package/front_end/panels/elements/EventListenersWidget.ts +0 -1
  21. package/front_end/panels/event_listeners/EventListenersView.ts +241 -297
  22. package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorController.ts +1 -1
  23. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -2
  24. package/front_end/panels/network/NetworkConfigView.ts +141 -116
  25. package/front_end/panels/network/NetworkPanel.ts +7 -4
  26. package/front_end/panels/network/RequestHeadersView.ts +1 -1
  27. package/front_end/panels/network/forward/BackendLinking.ts +5 -2
  28. package/front_end/panels/settings/BackendLinkingSettingsTab.ts +7 -2
  29. package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
  30. package/front_end/third_party/chromium/README.chromium +1 -1
  31. package/front_end/ui/comments/CommentOverlayManager.ts +169 -40
  32. package/front_end/ui/comments/CommentThreadWidget.ts +221 -0
  33. package/front_end/ui/comments/CommentsOverlayWidget.ts +124 -12
  34. package/front_end/ui/comments/commentThreadWidget.css +173 -0
  35. package/front_end/ui/comments/comments.ts +2 -0
  36. package/front_end/ui/comments/commentsOverlay.css +6 -0
  37. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +1 -1
  38. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +4 -0
  39. package/package.json +5 -5
package/.stylelintignore CHANGED
@@ -1,4 +1,5 @@
1
1
  front_end/third_party
2
+ extensions/cxx_debugging/third_party
2
3
  karma-coverage
3
4
  node_modules
4
5
  out
@@ -8,3 +9,4 @@ test/**/*.ts
8
9
  third_party
9
10
  front_end/ui/components/docs
10
11
  build
12
+ buildtools
@@ -17,7 +17,7 @@ export class VersionController {
17
17
  static readonly SYNCED_VERSION_SETTING_NAME = 'syncedInspectorVersion';
18
18
  static readonly LOCAL_VERSION_SETTING_NAME = 'localInspectorVersion';
19
19
 
20
- static readonly CURRENT_VERSION = 46;
20
+ static readonly CURRENT_VERSION = 47;
21
21
 
22
22
  readonly #settings: Settings;
23
23
  readonly #globalVersionSetting: Setting<number>;
@@ -897,6 +897,10 @@ export class VersionController {
897
897
  }
898
898
  }
899
899
 
900
+ updateVersionFrom46To47(): void {
901
+ this.#settings.syncedStorage.remove('network.backend-linking-rules');
902
+ }
903
+
900
904
  /*
901
905
  * Any new migration should be added before this comment.
902
906
  *
@@ -920,7 +920,15 @@ export class AnimationGroup {
920
920
  }
921
921
 
922
922
  seekTo(currentTime: number): void {
923
- void this.#animationModel.agent.invoke_seekAnimations({animations: this.animationIds(), currentTime});
923
+ const animations = [];
924
+ const currentTimes = [];
925
+ for (const animation of this.#animations) {
926
+ // Seek from the end of the animation if it is reversed.
927
+ animations.push(animation.id());
928
+ currentTimes.push(animation.playbackRate() >= 0 ? currentTime :
929
+ animation.endTime() - animation.startTime() - currentTime);
930
+ }
931
+ void this.#animationModel.agent.invoke_seekAnimations({animations, currentTimes});
924
932
  }
925
933
 
926
934
  paused(): boolean {
@@ -62,7 +62,7 @@ inspectorBackend.registerCommand("Animation.getCurrentTime", [{"name": "id", "ty
62
62
  inspectorBackend.registerCommand("Animation.getPlaybackRate", [], ["playbackRate"], "Gets the playback rate of the document timeline.");
63
63
  inspectorBackend.registerCommand("Animation.releaseAnimations", [{"name": "animations", "type": "array", "optional": false, "description": "List of animation ids to seek.", "typeRef": "string"}], [], "Releases a set of animations to no longer be manipulated.");
64
64
  inspectorBackend.registerCommand("Animation.resolveAnimation", [{"name": "animationId", "type": "string", "optional": false, "description": "Animation id.", "typeRef": null}], ["remoteObject"], "Gets the remote object of the Animation.");
65
- inspectorBackend.registerCommand("Animation.seekAnimations", [{"name": "animations", "type": "array", "optional": false, "description": "List of animation ids to seek.", "typeRef": "string"}, {"name": "currentTime", "type": "number", "optional": false, "description": "Set the current time of each animation.", "typeRef": null}], [], "Seek a set of animations to a particular time within each animation.");
65
+ inspectorBackend.registerCommand("Animation.seekAnimations", [{"name": "animations", "type": "array", "optional": false, "description": "List of animation ids to seek.", "typeRef": "string"}, {"name": "currentTime", "type": "number", "optional": true, "description": "Set each animation to the same time.", "typeRef": null}, {"name": "currentTimes", "type": "array", "optional": true, "description": "Set each animation to a different time. If set, should have the same length as animations. Exactly one of currentTime or currentTimes should be set.", "typeRef": "number"}], [], "Seek a set of animations to a particular time within each animation.");
66
66
  inspectorBackend.registerCommand("Animation.setPaused", [{"name": "animations", "type": "array", "optional": false, "description": "Animations to set the pause state of.", "typeRef": "string"}, {"name": "paused", "type": "boolean", "optional": false, "description": "Paused state to set to.", "typeRef": null}], [], "Sets the paused state of a set of animations.");
67
67
  inspectorBackend.registerCommand("Animation.setPlaybackRate", [{"name": "playbackRate", "type": "number", "optional": false, "description": "Playback rate for animations on page", "typeRef": null}], [], "Sets the playback rate of the document timeline.");
68
68
  inspectorBackend.registerCommand("Animation.setTiming", [{"name": "animationId", "type": "string", "optional": false, "description": "Animation id.", "typeRef": null}, {"name": "duration", "type": "number", "optional": false, "description": "Duration of the animation.", "typeRef": null}, {"name": "delay", "type": "number", "optional": false, "description": "Delay of the animation.", "typeRef": null}], [], "Sets the timing of an animation node.");
@@ -749,9 +749,15 @@ export namespace Animation {
749
749
  */
750
750
  animations: string[];
751
751
  /**
752
- * Set the current time of each animation.
752
+ * Set each animation to the same time.
753
753
  */
754
- currentTime: number;
754
+ currentTime?: number;
755
+ /**
756
+ * Set each animation to a different time. If set, should have the same
757
+ * length as animations. Exactly one of currentTime or currentTimes should
758
+ * be set.
759
+ */
760
+ currentTimes?: number[];
755
761
  }
756
762
 
757
763
  export interface SetPausedRequest {
@@ -20,6 +20,7 @@ import {StorageContext} from '../contexts/StorageContext.js';
20
20
  import {formatBytesToKb, seconds} from '../data_formatters/UnitFormatters.js';
21
21
  import {debugLog} from '../debug.js';
22
22
  import {StorageItem} from '../StorageItem.js';
23
+ import {isOriginAllowedByLock} from '../tools/Tool.js';
23
24
 
24
25
  import {
25
26
  type AgentOptions,
@@ -587,4 +588,29 @@ export class ContextSelectionAgent extends AiAgent<never> {
587
588
 
588
589
  return [...uiSourceCodes.values()];
589
590
  }
591
+
592
+ /**
593
+ * Resolves a workspace source file by its ID, ensuring that the file's security
594
+ * origin is authorized by the conversation's established origin lock.
595
+ *
596
+ * Fails closed by returning `undefined` if the established origin is missing or opaque,
597
+ * if the file ID is invalid, or if the file origin does not match the lock.
598
+ */
599
+ static getSourceById(
600
+ id: number,
601
+ establishedOrigin?: SDK.SecurityOrigin.SecurityOrigin,
602
+ // eslint-disable-next-line @devtools/no-instance-of-migrated-singletons
603
+ workspace: Workspace.Workspace.WorkspaceImpl = Workspace.Workspace.WorkspaceImpl.instance(),
604
+ ): Workspace.UISourceCode.UISourceCode|undefined {
605
+ if (!establishedOrigin || !Number.isInteger(id) || id <= 0) {
606
+ return undefined;
607
+ }
608
+ return ContextSelectionAgent.getUISourceCodes(workspace).find(file => {
609
+ if (ContextSelectionAgent.uiSourceCodeId.get(file) !== id) {
610
+ return false;
611
+ }
612
+ return isOriginAllowedByLock({status: 'ESTABLISHED_ORIGIN', origin: establishedOrigin},
613
+ FileContext.originForUISourceCode(file));
614
+ });
615
+ }
590
616
  }
@@ -46,18 +46,18 @@ import * as ExecuteJavaScript from './tools/ExecuteJavaScript.js';
46
46
  import * as GetCookieValues from './tools/GetCookieValues.js';
47
47
  import * as GetDetailedCallTree from './tools/GetDetailedCallTree.js';
48
48
  import * as GetElementAccessibilityDetails from './tools/GetElementAccessibilityDetails.js';
49
- import * as GetFunctionCode from './tools/GetFunctionCode.js';
50
49
  import * as GetInsightDetails from './tools/GetInsightDetails.js';
51
50
  import * as GetLighthouseAudits from './tools/GetLighthouseAudits.js';
52
51
  import * as GetNetworkRequestDetails from './tools/GetNetworkRequestDetails.js';
53
- import * as GetResourceContent from './tools/GetResourceContent.js';
54
52
  import * as GetSourceContent from './tools/GetSourceContent.js';
55
53
  import * as GetStorageBreakdown from './tools/GetStorageBreakdown.js';
56
54
  import * as GetStorageValues from './tools/GetStorageValues.js';
57
55
  import * as GetStyles from './tools/GetStyles.js';
58
56
  import * as GetTraceEventByKey from './tools/GetTraceEventByKey.js';
57
+ import * as GetTraceFunctionCode from './tools/GetTraceFunctionCode.js';
59
58
  import * as GetTraceMainThreadSummary from './tools/GetTraceMainThreadSummary.js';
60
59
  import * as GetTraceNetworkSummary from './tools/GetTraceNetworkSummary.js';
60
+ import * as GetTraceResourceContent from './tools/GetTraceResourceContent.js';
61
61
  import * as ListCookies from './tools/ListCookies.js';
62
62
  import * as ListNetworkRequests from './tools/ListNetworkRequests.js';
63
63
  import * as ListPageOrigins from './tools/ListPageOrigins.js';
@@ -99,18 +99,18 @@ export {
99
99
  GetCookieValues,
100
100
  GetDetailedCallTree,
101
101
  GetElementAccessibilityDetails,
102
- GetFunctionCode,
103
102
  GetInsightDetails,
104
103
  GetLighthouseAudits,
105
104
  GetNetworkRequestDetails,
106
- GetResourceContent,
107
105
  GetSourceContent,
108
106
  GetStorageBreakdown,
109
107
  GetStorageValues,
110
108
  GetStyles,
111
109
  GetTraceEventByKey,
110
+ GetTraceFunctionCode,
112
111
  GetTraceMainThreadSummary,
113
112
  GetTraceNetworkSummary,
113
+ GetTraceResourceContent,
114
114
  Injected,
115
115
  LighthouseFormatter,
116
116
  ListCookies,
@@ -8,8 +8,8 @@ allowed-tools:
8
8
  - getTraceMainThreadSummary
9
9
  - getTraceNetworkSummary
10
10
  - getDetailedCallTree
11
- - getFunctionCode
12
- - getResourceContent
11
+ - getTraceFunctionCode
12
+ - getTraceResourceContent
13
13
  - getInsightDetails
14
14
  ---
15
15
  You are an expert web performance assistant integrated into Chrome DevTools.
@@ -29,7 +29,8 @@ Your goal is to provide actionable advice about web page performance by analyzin
29
29
  3. **Call Tree, Event & Source Inspection**:
30
30
  - Use `getDetailedCallTree` to retrieve bottom-up execution trees for expensive main thread tasks.
31
31
  - Use `getTraceEventByKey` to inspect timing and payload data for individual events.
32
- - Use `getFunctionCode` or `getResourceContent` to inspect the source code and identify root causes.
32
+ - Use `getTraceFunctionCode` to inspect a specific function identified in the call tree, including line-by-line CPU profile execution costs.
33
+ - Use `getTraceResourceContent` to inspect the full text of a script or resource captured within the trace by URL.
33
34
 
34
35
  4. **UI Selection & Trace Recording**:
35
36
  - Use `selectTraceEventByKey` to reveal and select an event in the Performance Flamechart if requested.
@@ -21,54 +21,67 @@ const UIStringsNotTranslate = {
21
21
 
22
22
  const lockedString = i18n.i18n.lockedString;
23
23
 
24
- export interface GetFunctionCodeArgs extends ToolArgs {
24
+ /**
25
+ * Arguments for {@link GetTraceFunctionCodeTool}.
26
+ */
27
+ export interface GetTraceFunctionCodeArgs extends ToolArgs {
28
+ /** The URL of the script containing the function recorded in the performance trace. */
25
29
  scriptUrl: string;
30
+ /** The line number where the function is defined (0-based, as reported in the call tree). */
26
31
  line: number;
32
+ /** The column number where the function is defined (0-based, as reported in the call tree). */
27
33
  column: number;
28
34
  }
29
35
 
30
- export class GetFunctionCodeTool implements
31
- DataTool<GetFunctionCodeArgs, string, BaseToolCapability&PerformanceTraceCapability> {
32
- readonly name: ToolName = ToolName.GET_FUNCTION_CODE;
36
+ /**
37
+ * Retrieves function code and line-by-line CPU profile execution costs from the performance trace.
38
+ *
39
+ * Preconditions:
40
+ * - Requires an active, freshly recorded trace session (fails on imported traces).
41
+ * - Requires resource access via the performance trace context.
42
+ */
43
+ export class GetTraceFunctionCodeTool implements
44
+ DataTool<GetTraceFunctionCodeArgs, string, BaseToolCapability&PerformanceTraceCapability> {
45
+ readonly name: ToolName = ToolName.GET_TRACE_FUNCTION_CODE;
33
46
  readonly description: string =
34
- 'Retrieves the code for a function defined at the given location. The result is annotated with the runtime performance of each line of code.';
47
+ 'Retrieves the code for a function recorded in the performance trace at the specified location, annotated with line-by-line CPU runtime profiling execution costs. Do not call this tool unless a performance trace recording is actively loaded.';
35
48
 
36
- readonly parameters: Host.AidaClient.FunctionObjectParam<keyof GetFunctionCodeArgs> = {
49
+ readonly parameters: Host.AidaClient.FunctionObjectParam<keyof GetTraceFunctionCodeArgs> = {
37
50
  type: Host.AidaClient.ParametersTypes.OBJECT,
38
- description: 'Arguments for looking up function code.',
51
+ description: 'Arguments for looking up function code from the performance trace profile.',
39
52
  nullable: false,
40
53
  properties: {
41
54
  scriptUrl: {
42
55
  type: Host.AidaClient.ParametersTypes.STRING,
43
- description: 'The url of the function.',
56
+ description: 'The URL of the script containing the function recorded in the performance trace.',
44
57
  nullable: false,
45
58
  },
46
59
  line: {
47
60
  type: Host.AidaClient.ParametersTypes.INTEGER,
48
- description: 'The line number where the function is defined.',
61
+ description: 'The line number where the function is defined (0-based, as reported in the call tree).',
49
62
  nullable: false,
50
63
  },
51
64
  column: {
52
65
  type: Host.AidaClient.ParametersTypes.INTEGER,
53
- description: 'The column number where the function is defined.',
66
+ description: 'The column number where the function is defined (0-based, as reported in the call tree).',
54
67
  nullable: false,
55
68
  },
56
69
  },
57
70
  required: ['scriptUrl', 'line', 'column'],
58
71
  };
59
72
 
60
- displayInfoFromArgs(params: GetFunctionCodeArgs): {
73
+ displayInfoFromArgs(params: GetTraceFunctionCodeArgs): {
61
74
  title: string,
62
75
  action: string,
63
76
  } {
64
77
  return {
65
78
  title: lockedString(UIStringsNotTranslate.lookingUpFunctionCode),
66
- action: `getFunctionCode('${params.scriptUrl}', ${params.line}, ${params.column})`,
79
+ action: `getTraceFunctionCode('${params.scriptUrl}', ${params.line}, ${params.column})`,
67
80
  };
68
81
  }
69
82
 
70
83
  async handler(
71
- params: GetFunctionCodeArgs,
84
+ params: GetTraceFunctionCodeArgs,
72
85
  capabilities: BaseToolCapability&PerformanceTraceCapability,
73
86
  ): Promise<DataHandlerResult<string>> {
74
87
  const performanceTraceContext = capabilities.getPerformanceTraceContext();
@@ -25,42 +25,57 @@ const UIStringsNotTranslate = {
25
25
 
26
26
  const lockedString = i18n.i18n.lockedString;
27
27
 
28
- export interface GetResourceContentArgs extends ToolArgs {
28
+ /**
29
+ * Arguments for {@link GetTraceResourceContentTool}.
30
+ */
31
+ export interface GetTraceResourceContentArgs extends ToolArgs {
32
+ /** The URL of the resource captured in the performance trace to retrieve. */
29
33
  url: string;
30
34
  }
31
35
 
32
- export class GetResourceContentTool implements DataTool<
33
- GetResourceContentArgs, {content: string}, BaseToolCapability&TargetCapability&PerformanceTraceCapability> {
34
- readonly name: ToolName = ToolName.GET_RESOURCE_CONTENT;
36
+ /**
37
+ * Retrieves text content for a resource or script captured in the performance trace.
38
+ *
39
+ * Precedence:
40
+ * 1. Checks trace metadata (`parsedTrace.data.Scripts`) for scripts captured during recording.
41
+ * 2. Falls back to querying the live page target's `ResourceTreeModel`.
42
+ *
43
+ * Preconditions:
44
+ * - Requires an active, freshly recorded trace session (fails on imported traces).
45
+ * - Fails if resource is binary/non-text, cross-origin, or a `file://` URL.
46
+ */
47
+ export class GetTraceResourceContentTool implements DataTool<
48
+ GetTraceResourceContentArgs, {content: string}, BaseToolCapability&TargetCapability&PerformanceTraceCapability> {
49
+ readonly name: ToolName = ToolName.GET_TRACE_RESOURCE_CONTENT;
35
50
  readonly description: string =
36
- 'Retrieves the content of the resource with the given url. Only use this for text resource types.';
51
+ 'Retrieves the text content of a script or resource captured within the recorded performance trace by URL. Only use this for text resource types. Do not call this tool on imported traces or for general workspace files (use listSources and getSourceContent instead).';
37
52
 
38
- readonly parameters: Host.AidaClient.FunctionObjectParam<keyof GetResourceContentArgs> = {
53
+ readonly parameters: Host.AidaClient.FunctionObjectParam<keyof GetTraceResourceContentArgs> = {
39
54
  type: Host.AidaClient.ParametersTypes.OBJECT,
40
- description: 'Arguments for looking up resource content.',
55
+ description: 'Arguments for looking up resource content from the performance trace.',
41
56
  nullable: false,
42
57
  properties: {
43
58
  url: {
44
59
  type: Host.AidaClient.ParametersTypes.STRING,
45
- description: 'The url for the resource.',
60
+ description: 'The URL of the resource captured in the performance trace to retrieve.',
46
61
  nullable: false,
47
62
  },
48
63
  },
49
64
  required: ['url'],
50
65
  };
51
66
 
52
- displayInfoFromArgs(params: GetResourceContentArgs): {
67
+ displayInfoFromArgs(params: GetTraceResourceContentArgs): {
53
68
  title: string,
54
69
  action: string,
55
70
  } {
56
71
  return {
57
72
  title: lockedString(UIStringsNotTranslate.lookingAtResourceContent),
58
- action: `getResourceContent('${params.url}')`,
73
+ action: `getTraceResourceContent('${params.url}')`,
59
74
  };
60
75
  }
61
76
 
62
77
  async handler(
63
- params: GetResourceContentArgs,
78
+ params: GetTraceResourceContentArgs,
64
79
  capabilities: BaseToolCapability&TargetCapability&PerformanceTraceCapability,
65
80
  ): Promise<DataHandlerResult<{content: string}>> {
66
81
  const performanceTraceContext = capabilities.getPerformanceTraceContext();
@@ -72,6 +87,10 @@ export class GetResourceContentTool implements DataTool<
72
87
  return {error: 'Cannot use this tool on an imported file.'};
73
88
  }
74
89
 
90
+ if (!params.url) {
91
+ return {error: 'Missing arg: url'};
92
+ }
93
+
75
94
  if (!performanceTraceContext.canAccessResource(params.url)) {
76
95
  return {error: 'Resource not found'};
77
96
  }
@@ -82,6 +101,7 @@ export class GetResourceContentTool implements DataTool<
82
101
  let content: string;
83
102
 
84
103
  const url = params.url as Platform.DevToolsPath.UrlString;
104
+ // Check trace metadata for scripts captured during recording before falling back to live page resources.
85
105
  const script = parsedTrace.data.Scripts?.scripts.find(script => script.url === params.url);
86
106
  if (script?.content !== undefined) {
87
107
  content = script.content;
@@ -258,8 +258,8 @@ export const enum ToolName {
258
258
  GET_TRACE_NETWORK_SUMMARY = 'getTraceNetworkSummary',
259
259
  RUN_LIGHTHOUSE = 'runLighthouse',
260
260
  GET_DETAILED_CALL_TREE = 'getDetailedCallTree',
261
- GET_FUNCTION_CODE = 'getFunctionCode',
262
- GET_RESOURCE_CONTENT = 'getResourceContent',
261
+ GET_TRACE_FUNCTION_CODE = 'getTraceFunctionCode',
262
+ GET_TRACE_RESOURCE_CONTENT = 'getTraceResourceContent',
263
263
  GET_INSIGHT_DETAILS = 'getInsightDetails',
264
264
  GET_STORAGE_BREAKDOWN = 'getStorageBreakdown',
265
265
  }
@@ -6,18 +6,18 @@ import {ExecuteJavaScriptTool} from './ExecuteJavaScript.js';
6
6
  import {GetCookieValuesTool} from './GetCookieValues.js';
7
7
  import {GetDetailedCallTreeTool} from './GetDetailedCallTree.js';
8
8
  import {GetElementAccessibilityDetailsTool} from './GetElementAccessibilityDetails.js';
9
- import {GetFunctionCodeTool} from './GetFunctionCode.js';
10
9
  import {GetInsightDetailsTool} from './GetInsightDetails.js';
11
10
  import {GetLighthouseAuditsTool} from './GetLighthouseAudits.js';
12
11
  import {GetNetworkRequestDetailsTool} from './GetNetworkRequestDetails.js';
13
- import {GetResourceContentTool} from './GetResourceContent.js';
14
12
  import {GetSourceContentTool} from './GetSourceContent.js';
15
13
  import {GetStorageBreakdownTool} from './GetStorageBreakdown.js';
16
14
  import {GetStorageValuesTool} from './GetStorageValues.js';
17
15
  import {GetStylesTool} from './GetStyles.js';
18
16
  import {GetTraceEventByKeyTool} from './GetTraceEventByKey.js';
17
+ import {GetTraceFunctionCodeTool} from './GetTraceFunctionCode.js';
19
18
  import {GetTraceMainThreadSummaryTool} from './GetTraceMainThreadSummary.js';
20
19
  import {GetTraceNetworkSummaryTool} from './GetTraceNetworkSummary.js';
20
+ import {GetTraceResourceContentTool} from './GetTraceResourceContent.js';
21
21
  import {ListCookiesTool} from './ListCookies.js';
22
22
  import {ListNetworkRequestsTool} from './ListNetworkRequests.js';
23
23
  import {ListPageOriginsTool} from './ListPageOrigins.js';
@@ -51,8 +51,8 @@ export interface BaseToolsRegistryMap {
51
51
  [ToolName.GET_TRACE_NETWORK_SUMMARY]: GetTraceNetworkSummaryTool;
52
52
  [ToolName.RUN_LIGHTHOUSE]: RunLighthouseTool;
53
53
  [ToolName.GET_DETAILED_CALL_TREE]: GetDetailedCallTreeTool;
54
- [ToolName.GET_FUNCTION_CODE]: GetFunctionCodeTool;
55
- [ToolName.GET_RESOURCE_CONTENT]: GetResourceContentTool;
54
+ [ToolName.GET_TRACE_FUNCTION_CODE]: GetTraceFunctionCodeTool;
55
+ [ToolName.GET_TRACE_RESOURCE_CONTENT]: GetTraceResourceContentTool;
56
56
  [ToolName.GET_INSIGHT_DETAILS]: GetInsightDetailsTool;
57
57
  [ToolName.GET_STORAGE_BREAKDOWN]: GetStorageBreakdownTool;
58
58
  }
@@ -86,8 +86,8 @@ export const TOOLS: BaseToolsRegistryMap = {
86
86
  [ToolName.GET_TRACE_NETWORK_SUMMARY]: new GetTraceNetworkSummaryTool(),
87
87
  [ToolName.RUN_LIGHTHOUSE]: new RunLighthouseTool(),
88
88
  [ToolName.GET_DETAILED_CALL_TREE]: new GetDetailedCallTreeTool(),
89
- [ToolName.GET_FUNCTION_CODE]: new GetFunctionCodeTool(),
90
- [ToolName.GET_RESOURCE_CONTENT]: new GetResourceContentTool(),
89
+ [ToolName.GET_TRACE_FUNCTION_CODE]: new GetTraceFunctionCodeTool(),
90
+ [ToolName.GET_TRACE_RESOURCE_CONTENT]: new GetTraceResourceContentTool(),
91
91
  [ToolName.GET_INSIGHT_DETAILS]: new GetInsightDetailsTool(),
92
92
  [ToolName.GET_STORAGE_BREAKDOWN]: new GetStorageBreakdownTool(),
93
93
  };
@@ -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
  };