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
@@ -5,11 +5,11 @@
5
5
  /* eslint-disable @devtools/prefer-private-class-members */
6
6
 
7
7
  import * as i18n from '../../core/i18n/i18n.js';
8
- import type * as PlatformApi from '../../core/platform/api/api.js';
9
8
  import * as Platform from '../../core/platform/platform.js';
10
9
  import * as HeapSnapshotModel from '../../models/heap_snapshot/heap_snapshot.js';
11
10
 
12
11
  import {AllocationProfile} from './AllocationProfile.js';
12
+ import * as ContextAnalyzer from './ContextAnalyzer.js';
13
13
  import type {HeapSnapshotWorkerDispatcher} from './HeapSnapshotWorkerDispatcher.js';
14
14
 
15
15
  export interface HeapSnapshotItem {
@@ -724,8 +724,8 @@ function formatProblemReport(snapshot: HeapSnapshot, report: HeapSnapshotProblem
724
724
  })
725
725
  .join('\n ');
726
726
  }
727
- function reportProblemToPrimaryWorker(
728
- problemReport: HeapSnapshotProblemReport, port: PlatformApi.HostRuntime.WorkerMessagePort): void {
727
+ function reportProblemToPrimaryWorker(problemReport: HeapSnapshotProblemReport,
728
+ port: Platform.HostRuntime.WorkerMessagePort): void {
729
729
  port.postMessage({problemReport});
730
730
  }
731
731
 
@@ -738,6 +738,9 @@ export interface Profile {
738
738
  samples: number[];
739
739
  strings: string[];
740
740
  locations: number[];
741
+ scopes?: number[];
742
+ scope_context_vars?: number[];
743
+ scope_uses?: number[];
741
744
  trace_function_infos: Uint32Array;
742
745
  trace_tree: Object;
743
746
  /* eslint-enable @typescript-eslint/naming-convention */
@@ -790,7 +793,7 @@ interface ArgumentsToComputeDominatorsAndRetainedSizes extends SecondaryInitArgu
790
793
  // should be used when computing dominators.
791
794
  essentialEdges: Platform.TypedArrayUtilities.BitVector;
792
795
  // A message port for reporting problems to the primary worker.
793
- port: PlatformApi.HostRuntime.WorkerMessagePort;
796
+ port: Platform.HostRuntime.WorkerMessagePort;
794
797
  // For each node ordinal, this array will contain the node's shallow size.
795
798
  nodeSelfSizesPromise: Promise<Uint32Array>;
796
799
  }
@@ -825,7 +828,7 @@ export class SecondaryInitManager {
825
828
  argsStep1: Promise<SecondaryInitArgumentsStep1>;
826
829
  argsStep2: Promise<SecondaryInitArgumentsStep2>;
827
830
  argsStep3: Promise<SecondaryInitArgumentsStep3>;
828
- constructor(port: PlatformApi.HostRuntime.WorkerMessagePort) {
831
+ constructor(port: Platform.HostRuntime.WorkerMessagePort) {
829
832
  const {promise: argsStep1, resolve: resolveArgsStep1} = Promise.withResolvers<SecondaryInitArgumentsStep1>();
830
833
  this.argsStep1 = argsStep1;
831
834
  const {promise: argsStep2, resolve: resolveArgsStep2} = Promise.withResolvers<SecondaryInitArgumentsStep2>();
@@ -833,7 +836,7 @@ export class SecondaryInitManager {
833
836
  const {promise: argsStep3, resolve: resolveArgsStep3} = Promise.withResolvers<SecondaryInitArgumentsStep3>();
834
837
  this.argsStep3 = argsStep3;
835
838
  const listener = (e: unknown): void => {
836
- const data = (e as PlatformApi.HostRuntime.WorkerMessageEvent).data;
839
+ const data = (e as Platform.HostRuntime.WorkerMessageEvent).data;
837
840
  switch (data.step) {
838
841
  case 1:
839
842
  resolveArgsStep1(data.args);
@@ -856,7 +859,7 @@ export class SecondaryInitManager {
856
859
  return (await this.argsStep3).nodeSelfSizes;
857
860
  }
858
861
 
859
- private async initialize(port: PlatformApi.HostRuntime.WorkerMessagePort): Promise<void> {
862
+ private async initialize(port: Platform.HostRuntime.WorkerMessagePort): Promise<void> {
860
863
  try {
861
864
  const argsStep1 = await this.argsStep1;
862
865
  const retainers = HeapSnapshot.buildRetainers(argsStep1);
@@ -1024,7 +1027,7 @@ export abstract class HeapSnapshot {
1024
1027
  this.#edgeNamesThatAreNotWeakMaps = Platform.TypedArrayUtilities.createBitVector(this.strings.length);
1025
1028
  }
1026
1029
 
1027
- async initialize(secondWorker: PlatformApi.HostRuntime.WorkerMessagePort): Promise<void> {
1030
+ async initialize(secondWorker: Platform.HostRuntime.WorkerMessagePort): Promise<void> {
1028
1031
  const meta = this.#metaNode;
1029
1032
 
1030
1033
  this.nodeTypeOffset = meta.node_fields.indexOf('type');
@@ -1170,11 +1173,19 @@ export abstract class HeapSnapshot {
1170
1173
  };
1171
1174
  }
1172
1175
 
1173
- private startInitStep1InSecondThread(secondWorker: PlatformApi.HostRuntime.WorkerMessagePort):
1176
+ /**
1177
+ * Computes for every context field whether it is still live, i.e. whether it
1178
+ * can be reached by some closure, and reports the dead fields.
1179
+ */
1180
+ analyzeContexts(): HeapSnapshotModel.HeapSnapshotModel.ContextAnalysisResult {
1181
+ return ContextAnalyzer.analyzeContexts(this);
1182
+ }
1183
+
1184
+ private startInitStep1InSecondThread(secondWorker: Platform.HostRuntime.WorkerMessagePort):
1174
1185
  Promise<ResultsFromSecondWorker> {
1175
1186
  const resultsFromSecondWorker = new Promise<ResultsFromSecondWorker>((resolve, reject) => {
1176
1187
  const listener = (e: unknown): void => {
1177
- const data = (e as PlatformApi.HostRuntime.WorkerMessageEvent).data;
1188
+ const data = (e as Platform.HostRuntime.WorkerMessageEvent).data;
1178
1189
  if (data?.problemReport) {
1179
1190
  const problemReport: HeapSnapshotProblemReport = data.problemReport;
1180
1191
  console.warn(formatProblemReport(this, problemReport));
@@ -1211,14 +1222,14 @@ export abstract class HeapSnapshot {
1211
1222
  return resultsFromSecondWorker;
1212
1223
  }
1213
1224
 
1214
- private startInitStep2InSecondThread(secondWorker: PlatformApi.HostRuntime.WorkerMessagePort): void {
1225
+ private startInitStep2InSecondThread(secondWorker: Platform.HostRuntime.WorkerMessagePort): void {
1215
1226
  const rootNodeOrdinal = this.rootNodeIndexInternal / this.nodeFieldCount;
1216
1227
  const essentialEdges = this.initEssentialEdges();
1217
1228
  const args: SecondaryInitArgumentsStep2 = {rootNodeOrdinal, essentialEdgesBuffer: essentialEdges.buffer};
1218
1229
  secondWorker.postMessage({step: 2, args}, [essentialEdges.buffer]);
1219
1230
  }
1220
1231
 
1221
- private startInitStep3InSecondThread(secondWorker: PlatformApi.HostRuntime.WorkerMessagePort): void {
1232
+ private startInitStep3InSecondThread(secondWorker: Platform.HostRuntime.WorkerMessagePort): void {
1222
1233
  const {nodes, nodeFieldCount, nodeSelfSizeOffset, nodeCount} = this;
1223
1234
  const nodeSelfSizes = new Uint32Array(nodeCount);
1224
1235
  for (let nodeOrdinal = 0; nodeOrdinal < nodeCount; ++nodeOrdinal) {
@@ -3837,6 +3848,9 @@ interface HeapSnapshotMetaInfo {
3837
3848
  trace_node_fields: string[];
3838
3849
  sample_fields: string[];
3839
3850
  type_strings: Record<string, string>;
3851
+ scope_fields?: string[];
3852
+ scope_context_var_fields?: string[];
3853
+ scope_use_fields?: string[];
3840
3854
  /* eslint-enable @typescript-eslint/naming-convention */
3841
3855
  }
3842
3856
 
@@ -2,7 +2,6 @@
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 type * as PlatformApi from '../../core/platform/api/api.js';
6
5
  import * as Platform from '../../core/platform/platform.js';
7
6
  import * as TextUtils from '../../core/text_utils/text_utils.js';
8
7
 
@@ -49,7 +48,7 @@ export class HeapSnapshotLoader {
49
48
  }
50
49
  }
51
50
 
52
- async buildSnapshot(secondWorker: PlatformApi.HostRuntime.WorkerMessagePort): Promise<JSHeapSnapshot> {
51
+ async buildSnapshot(secondWorker: Platform.HostRuntime.WorkerMessagePort): Promise<JSHeapSnapshot> {
53
52
  await this.parsingComplete;
54
53
 
55
54
  this.#snapshot = this.#snapshot || {};
@@ -252,6 +251,21 @@ export class HeapSnapshotLoader {
252
251
  this.#snapshot.locations = [];
253
252
  }
254
253
 
254
+ if (this.#snapshot.snapshot.meta.scope_fields) {
255
+ const scopes = await this.#parseArray('"scopes"', 'Loading scopes…');
256
+ this.#snapshot.scopes = scopes.asArrayOrFail();
257
+ }
258
+
259
+ if (this.#snapshot.snapshot.meta.scope_context_var_fields) {
260
+ const scopeContextVars = await this.#parseArray('"scope_context_vars"', 'Loading scope context vars…');
261
+ this.#snapshot.scope_context_vars = scopeContextVars.asArrayOrFail();
262
+ }
263
+
264
+ if (this.#snapshot.snapshot.meta.scope_use_fields) {
265
+ const scopeUses = await this.#parseArray('"scope_uses"', 'Loading scope uses…');
266
+ this.#snapshot.scope_uses = scopeUses.asArrayOrFail();
267
+ }
268
+
255
269
  this.#progress.updateStatus('Loading strings…');
256
270
  const stringsTokenIndex = await this.#findToken('"strings"');
257
271
  const bracketIndex = await this.#findToken('[', stringsTokenIndex);
@@ -2,7 +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 type * as PlatformApi from '../../core/platform/api/api.js';
5
+ import type * as Platform from '../../core/platform/platform.js';
6
6
  import * as HeapSnapshotModel from '../../models/heap_snapshot/heap_snapshot.js';
7
7
 
8
8
  // We mirror what heap_snapshot_worker.ts does, but we can't use it here as we'd have a
@@ -24,8 +24,8 @@ export class HeapSnapshotWorkerDispatcher {
24
24
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
25
25
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
26
  #objects: any[] = [];
27
- readonly #postMessage: PlatformApi.HostRuntime.Worker['postMessage'];
28
- constructor(postMessage: PlatformApi.HostRuntime.Worker['postMessage']) {
27
+ readonly #postMessage: Platform.HostRuntime.Worker['postMessage'];
28
+ constructor(postMessage: Platform.HostRuntime.Worker['postMessage']) {
29
29
  this.#postMessage = postMessage;
30
30
  }
31
31
 
@@ -36,7 +36,7 @@ export class HeapSnapshotWorkerDispatcher {
36
36
  async dispatchMessage({
37
37
  data,
38
38
  ports,
39
- }: PlatformApi.HostRuntime.WorkerMessageEvent<HeapSnapshotModel.HeapSnapshotModel.WorkerCommand>): Promise<void> {
39
+ }: Platform.HostRuntime.WorkerMessageEvent<HeapSnapshotModel.HeapSnapshotModel.WorkerCommand>): Promise<void> {
40
40
  const response: DispatcherResponse = {
41
41
  callId: data.callId,
42
42
  result: null,
@@ -3,12 +3,14 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as AllocationProfile from './AllocationProfile.js';
6
+ import * as ContextAnalyzer from './ContextAnalyzer.js';
6
7
  import * as HeapSnapshot from './HeapSnapshot.js';
7
8
  import * as HeapSnapshotLoader from './HeapSnapshotLoader.js';
8
9
  import * as HeapSnapshotWorkerDispatcher from './HeapSnapshotWorkerDispatcher.js';
9
10
 
10
11
  export {
11
12
  AllocationProfile,
13
+ ContextAnalyzer,
12
14
  HeapSnapshot,
13
15
  HeapSnapshotLoader,
14
16
  HeapSnapshotWorkerDispatcher,
@@ -23,7 +23,7 @@ import '../../ui/legacy/components/source_frame/source_frame-meta.js';
23
23
  import '../../panels/console_counters/console_counters-meta.js';
24
24
  import '../../panels/explain/explain-meta.js';
25
25
  import '../../panels/ai_assistance/ai_assistance-meta.js';
26
- import '../../ui/comments/comments-meta.js';
26
+ import '../../panels/common/common-meta.js';
27
27
  import '../main/main.js';
28
28
 
29
29
  // We generate the descriptors in this file, which depend on the runtime.
@@ -0,0 +1,95 @@
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
+ /**
6
+ * @file This file includes DOM helpers and page-evaluated serialization helpers
7
+ * used by AI assistance that require DOM types. They are isolated in a separate
8
+ * target so DOM types do not leak into the rest of models/ai_assistance.
9
+ */
10
+
11
+ import * as Platform from '../../core/platform/platform.js';
12
+
13
+ export interface GetErrorStackOutput {
14
+ message: string;
15
+ stack?: string;
16
+ }
17
+
18
+ /* istanbul ignore next */
19
+ export function getErrorStackOnThePage(this: Error): GetErrorStackOutput {
20
+ // Using this.stack causes side effect checks to throw.
21
+ return {stack: '', message: this.message};
22
+ }
23
+
24
+ /* istanbul ignore next */
25
+ export function stringifyObjectOnThePage(this: unknown): string {
26
+ const seenBefore = new Map();
27
+ return JSON.stringify(this, function replacer(this: unknown, key: string, value: unknown) {
28
+ if (typeof value === 'object' && value !== null) {
29
+ if (seenBefore.has(value)) {
30
+ return '(cycle)';
31
+ }
32
+
33
+ seenBefore.set(value, true);
34
+ }
35
+
36
+ if (value instanceof HTMLElement) {
37
+ const idAttribute = value.id ? ` id="${value.id}"` : '';
38
+ const classAttribute = value.classList.value ? ` class="${value.classList.value}"` : '';
39
+
40
+ return `<${value.nodeName.toLowerCase()}${idAttribute}${classAttribute}>${value.hasChildNodes() ? '...' : ''}</${
41
+ value.nodeName.toLowerCase()}>`;
42
+ }
43
+
44
+ if (this instanceof CSSStyleDeclaration) {
45
+ // Do not add number keys to the output.
46
+ if (!isNaN(Number(key))) {
47
+ return undefined;
48
+ }
49
+ }
50
+
51
+ return value;
52
+ });
53
+ }
54
+
55
+ export async function sanitizeStyleChanges(selector: string,
56
+ styles: Record<string, string>): Promise<Record<string, string>> {
57
+ const cssStyleValue: string[] = [];
58
+ const changedStyles: string[] = [];
59
+ const kebabStyles = Platform.StringUtilities.toKebabCaseKeys(styles);
60
+ for (const [style, value] of Object.entries(kebabStyles)) {
61
+ // Build up the CSS style
62
+ cssStyleValue.push(`${style}: ${value};`);
63
+ // Keep track of what style changed to query later.
64
+ changedStyles.push(style);
65
+ }
66
+
67
+ if (typeof CSSStyleSheet === 'undefined' || typeof CSSStyleRule === 'undefined') {
68
+ return kebabStyles;
69
+ }
70
+
71
+ const styleSheet = new CSSStyleSheet({disabled: true});
72
+ // Build up the CSS stylesheet value.
73
+ await styleSheet.replace(`${selector} { ${cssStyleValue.join(' ')} }`);
74
+
75
+ const sanitizedStyles: Record<string, string> = {};
76
+ for (const cssRule of styleSheet.cssRules) {
77
+ if (!(cssRule instanceof CSSStyleRule)) {
78
+ continue;
79
+ }
80
+ for (const style of changedStyles) {
81
+ const value = cssRule.style.getPropertyValue(style);
82
+ if (value) {
83
+ sanitizedStyles[style] = value;
84
+ }
85
+ }
86
+ }
87
+
88
+ return sanitizedStyles;
89
+ }
90
+
91
+ export function dispatchAiAssistanceDoneEvent(): void {
92
+ if (typeof window !== 'undefined') {
93
+ window.dispatchEvent(new CustomEvent('aiassistancedone'));
94
+ }
95
+ }
@@ -5,6 +5,11 @@
5
5
  import * as SDK from '../../core/sdk/sdk.js';
6
6
  import * as Protocol from '../../generated/protocol.js';
7
7
 
8
+ import {
9
+ getErrorStackOnThePage,
10
+ type GetErrorStackOutput,
11
+ stringifyObjectOnThePage,
12
+ } from './DOMHelpers.js';
8
13
  import {PAGE_EXPOSED_FUNCTIONS} from './injected.js';
9
14
 
10
15
  export function formatError(message: string): string {
@@ -12,48 +17,6 @@ export function formatError(message: string): string {
12
17
  }
13
18
  export class SideEffectError extends Error {}
14
19
 
15
- export interface GetErrorStackOutput {
16
- message: string;
17
- stack?: string;
18
- }
19
-
20
- /* istanbul ignore next */
21
- export function getErrorStackOnThePage(this: Error): GetErrorStackOutput {
22
- // Using this.stack causes side effect checks to throw.
23
- return {stack: '', message: this.message};
24
- }
25
-
26
- /* istanbul ignore next */
27
- export function stringifyObjectOnThePage(this: unknown): string {
28
- const seenBefore = new Map();
29
- return JSON.stringify(this, function replacer(this: unknown, key: string, value: unknown) {
30
- if (typeof value === 'object' && value !== null) {
31
- if (seenBefore.has(value)) {
32
- return '(cycle)';
33
- }
34
-
35
- seenBefore.set(value, true);
36
- }
37
-
38
- if (value instanceof HTMLElement) {
39
- const idAttribute = value.id ? ` id="${value.id}"` : '';
40
- const classAttribute = value.classList.value ? ` class="${value.classList.value}"` : '';
41
-
42
- return `<${value.nodeName.toLowerCase()}${idAttribute}${classAttribute}>${value.hasChildNodes() ? '...' : ''}</${
43
- value.nodeName.toLowerCase()}>`;
44
- }
45
-
46
- if (this instanceof CSSStyleDeclaration) {
47
- // Do not add number keys to the output.
48
- if (!isNaN(Number(key))) {
49
- return undefined;
50
- }
51
- }
52
-
53
- return value;
54
- });
55
- }
56
-
57
20
  export async function stringifyRemoteObject(
58
21
  object: SDK.RemoteObject.RemoteObject, functionDeclaration: string): Promise<string> {
59
22
  switch (object.type) {
@@ -3,12 +3,12 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as Common from '../../core/common/common.js';
6
- import * as Platform from '../../core/platform/platform.js';
7
6
  import * as SDK from '../../core/sdk/sdk.js';
8
7
  import * as Protocol from '../../generated/protocol.js';
9
8
 
10
9
  import {getOrCreateIsolatedWorld} from './agents/ExecuteJavascript.js';
11
10
  import type {ChangeManager} from './ChangeManager.js';
11
+ import {sanitizeStyleChanges} from './DOMHelpers.js';
12
12
  import {
13
13
  AI_ASSISTANCE_CSS_CLASS_NAME,
14
14
  type FreestyleCallbackArgs,
@@ -330,36 +330,7 @@ export class ExtensionScope {
330
330
  }
331
331
 
332
332
  async sanitizedStyleChanges(selector: string, styles: Record<string, string>): Promise<Record<string, string>> {
333
- const cssStyleValue: string[] = [];
334
- const changedStyles: string[] = [];
335
- const styleSheet = new CSSStyleSheet({disabled: true});
336
- const kebabStyles = Platform.StringUtilities.toKebabCaseKeys(styles);
337
- for (const [style, value] of Object.entries(kebabStyles)) {
338
- // Build up the CSS style
339
- cssStyleValue.push(`${style}: ${value};`);
340
- // Keep track of what style changed to query later.
341
- changedStyles.push(style);
342
- }
343
-
344
- // Build up the CSS stylesheet value.
345
- await styleSheet.replace(`${selector} { ${cssStyleValue.join(' ')} }`);
346
-
347
- const sanitizedStyles: Record<string, string> = {};
348
- for (const cssRule of styleSheet.cssRules) {
349
- if (!(cssRule instanceof CSSStyleRule)) {
350
- continue;
351
- }
352
- for (const style of changedStyles) {
353
- // We need to use the style rather then the stylesMap
354
- // as the latter expands the styles to each separate part
355
- // Example:
356
- // padding: 10px 20px -> padding-top: 10px, padding-bottom: 10px, etc.
357
- const value = cssRule.style.getPropertyValue(style);
358
- if (value) {
359
- sanitizedStyles[style] = value;
360
- }
361
- }
362
- }
333
+ const sanitizedStyles = await sanitizeStyleChanges(selector, styles);
363
334
 
364
335
  if (Object.keys(sanitizedStyles).length === 0) {
365
336
  throw new Error(
@@ -12,6 +12,7 @@ import type * as LHModel from '../../lighthouse/lighthouse.js';
12
12
  import type * as Trace from '../../trace/trace.js';
13
13
  import type * as Workspace from '../../workspace/workspace.js';
14
14
  import {debugLog, isStructuredLogEnabled} from '../debug.js';
15
+ import {dispatchAiAssistanceDoneEvent} from '../DOMHelpers.js';
15
16
  import type {ContextHandlerResult, DataHandlerResult} from '../tools/Tool.js';
16
17
 
17
18
  type UrlString = Platform.DevToolsPath.UrlString;
@@ -956,7 +957,7 @@ export abstract class AiAgent<T> {
956
957
  }
957
958
 
958
959
  if (isStructuredLogEnabled()) {
959
- window.dispatchEvent(new CustomEvent('aiassistancedone'));
960
+ dispatchAiAssistanceDoneEvent();
960
961
  }
961
962
  return;
962
963
  }
@@ -292,7 +292,7 @@ export class ContextSelectionAgent extends AiAgent<never> {
292
292
  const files: Array<{file: string, id: number | undefined}> = [];
293
293
  const uiSourceCodes: Workspace.UISourceCode.UISourceCode[] = [];
294
294
  for (const file of ContextSelectionAgent.getUISourceCodes(this.#workspace)) {
295
- const fileSecurityOrigin = FileContext.originForUISourceCode(file);
295
+ const fileSecurityOrigin = file.securityOrigin();
296
296
 
297
297
  if (origin && !fileSecurityOrigin.isSameOriginWith(origin)) {
298
298
  continue;
@@ -352,7 +352,7 @@ export class ContextSelectionAgent extends AiAgent<never> {
352
352
  if (ContextSelectionAgent.uiSourceCodeId.get(file) !== params.id) {
353
353
  return false;
354
354
  }
355
- const fileSecurityOrigin = FileContext.originForUISourceCode(file);
355
+ const fileSecurityOrigin = file.securityOrigin();
356
356
  return !origin || fileSecurityOrigin.isSameOriginWith(origin);
357
357
  });
358
358
 
@@ -609,8 +609,7 @@ export class ContextSelectionAgent extends AiAgent<never> {
609
609
  if (ContextSelectionAgent.uiSourceCodeId.get(file) !== id) {
610
610
  return false;
611
611
  }
612
- return isOriginAllowedByLock({status: 'ESTABLISHED_ORIGIN', origin: establishedOrigin},
613
- FileContext.originForUISourceCode(file));
612
+ return isOriginAllowedByLock({status: 'ESTABLISHED_ORIGIN', origin: establishedOrigin}, file.securityOrigin());
614
613
  });
615
614
  }
616
615
  }
@@ -30,6 +30,7 @@ import * as PerformanceInsightFormatter from './data_formatters/PerformanceInsig
30
30
  import * as PerformanceTraceFormatter from './data_formatters/PerformanceTraceFormatter.js';
31
31
  import * as UnitFormatters from './data_formatters/UnitFormatters.js';
32
32
  import * as Debug from './debug.js';
33
+ import * as DOMHelpers from './DOMHelpers.js';
33
34
  import * as EvaluateAction from './EvaluateAction.js';
34
35
  import * as ExtensionScope from './ExtensionScope.js';
35
36
  import * as Injected from './injected.js';
@@ -88,6 +89,7 @@ export {
88
89
  ConversationSummary,
89
90
  CookieUtils,
90
91
  Debug,
92
+ DOMHelpers,
91
93
  DOMNodeContext,
92
94
  DOMStorageUtils,
93
95
  EvaluateAction,
@@ -124,8 +124,9 @@ ${await this.describe()}`;
124
124
  * Its selector is \`${element.simpleSelector()}\``;
125
125
  const childNodes = await element.getChildNodesPromise();
126
126
  if (childNodes) {
127
- const textChildNodes = childNodes.filter(childNode => childNode.nodeType() === Node.TEXT_NODE);
128
- const elementChildNodes = childNodes.filter(childNode => childNode.nodeType() === Node.ELEMENT_NODE);
127
+ const textChildNodes = childNodes.filter(childNode => childNode.nodeType() === SDK.DOMModel.NodeType.TEXT_NODE);
128
+ const elementChildNodes =
129
+ childNodes.filter(childNode => childNode.nodeType() === SDK.DOMModel.NodeType.ELEMENT_NODE);
129
130
  switch (elementChildNodes.length) {
130
131
  case 0:
131
132
  output += '\n* It doesn\'t have any child element nodes';
@@ -151,14 +152,14 @@ ${await this.describe()}`;
151
152
  }
152
153
 
153
154
  if (element.nextSibling) {
154
- const elementOrNodeElementNodeText = element.nextSibling.nodeType() === Node.ELEMENT_NODE ?
155
+ const elementOrNodeElementNodeText = element.nextSibling.nodeType() === SDK.DOMModel.NodeType.ELEMENT_NODE ?
155
156
  `an element (uid=${element.nextSibling.backendNodeId()})` :
156
157
  'a non element';
157
158
  output += `\n* It has a next sibling and it is ${elementOrNodeElementNodeText} node`;
158
159
  }
159
160
 
160
161
  if (element.previousSibling) {
161
- const elementOrNodeElementNodeText = element.previousSibling.nodeType() === Node.ELEMENT_NODE ?
162
+ const elementOrNodeElementNodeText = element.previousSibling.nodeType() === SDK.DOMModel.NodeType.ELEMENT_NODE ?
162
163
  `an element (uid=${element.previousSibling.backendNodeId()})` :
163
164
  'a non element';
164
165
  output += `\n* It has a previous sibling and it is ${elementOrNodeElementNodeText} node`;
@@ -172,14 +173,15 @@ ${await this.describe()}`;
172
173
  if (parentNode) {
173
174
  const parentChildrenNodes = await parentNode.getChildNodesPromise();
174
175
  output += `\n* Its parent's selector is \`${parentNode.simpleSelector()}\` (uid=${parentNode.backendNodeId()})`;
175
- const elementOrNodeElementNodeText = parentNode.nodeType() === Node.ELEMENT_NODE ? 'an element' : 'a non element';
176
+ const elementOrNodeElementNodeText =
177
+ parentNode.nodeType() === SDK.DOMModel.NodeType.ELEMENT_NODE ? 'an element' : 'a non element';
176
178
  output += `\n* Its parent is ${elementOrNodeElementNodeText} node`;
177
179
  if (parentNode.isShadowRoot()) {
178
180
  output += '\n* Its parent is a shadow root.';
179
181
  }
180
182
  if (parentChildrenNodes) {
181
183
  const childElementNodes =
182
- parentChildrenNodes.filter(siblingNode => siblingNode.nodeType() === Node.ELEMENT_NODE);
184
+ parentChildrenNodes.filter(siblingNode => siblingNode.nodeType() === SDK.DOMModel.NodeType.ELEMENT_NODE);
183
185
  switch (childElementNodes.length) {
184
186
  case 0:
185
187
  break;
@@ -193,7 +195,8 @@ ${await this.describe()}`;
193
195
  break;
194
196
  }
195
197
 
196
- const siblingTextNodes = parentChildrenNodes.filter(siblingNode => siblingNode.nodeType() === Node.TEXT_NODE);
198
+ const siblingTextNodes =
199
+ parentChildrenNodes.filter(siblingNode => siblingNode.nodeType() === SDK.DOMModel.NodeType.TEXT_NODE);
197
200
  switch (siblingTextNodes.length) {
198
201
  case 0:
199
202
  break;
@@ -2,7 +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 SDK from '../../../core/sdk/sdk.js';
5
+ import type * as SDK from '../../../core/sdk/sdk.js';
6
6
  import type * as Bindings from '../../bindings/bindings.js';
7
7
  import type * as Workspace from '../../workspace/workspace.js';
8
8
  import {type ContextDetail, ConversationContext} from '../agents/AiAgent.js';
@@ -20,20 +20,12 @@ export class FileContext extends ConversationContext<Workspace.UISourceCode.UISo
20
20
  this.#debuggerWorkspaceBinding = debuggerWorkspaceBinding;
21
21
  }
22
22
 
23
- /**
24
- * Resolves the security origin of a given UISourceCode.
25
- * Prefers the project security origin, falling back to the origin of the file URL.
26
- */
27
- static originForUISourceCode(file: Workspace.UISourceCode.UISourceCode): SDK.SecurityOrigin.SecurityOrigin {
28
- return file.project()?.securityOrigin?.() ?? SDK.SecurityOrigin.SecurityOrigin.create(file.url());
29
- }
30
-
31
23
  /**
32
24
  * Returns the security origin of the project containing the file, falling
33
25
  * back to the origin derived from the file URL.
34
26
  */
35
27
  override getOrigin(): SDK.SecurityOrigin.SecurityOrigin {
36
- return FileContext.originForUISourceCode(this.#file);
28
+ return this.#file.securityOrigin();
37
29
  }
38
30
 
39
31
  override getItem(): Workspace.UISourceCode.UISourceCode {
@@ -65,12 +65,13 @@ export class GetSourceContentTool implements
65
65
  args: GetSourceContentArgs,
66
66
  context: BaseToolCapability&OriginLockCapability,
67
67
  ): Promise<DataHandlerResult<{content: string}>> {
68
- const originResult = resolveOriginFromLock(context.getOriginLock());
68
+ const originLock = context.getOriginLock();
69
+ const originResult = resolveOriginFromLock(originLock);
69
70
  if ('error' in originResult) {
70
71
  return originResult;
71
72
  }
72
73
 
73
- const file = ListSourcesTool.getSourceById(args.id, originResult.origin);
74
+ const file = ListSourcesTool.getSourceById(args.id, originLock);
74
75
 
75
76
  if (!file) {
76
77
  return {