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
@@ -23,7 +23,7 @@ import {
23
23
  pauseOnExceptionEnabledSettingDescriptor,
24
24
  pauseOnUncaughtExceptionSettingDescriptor,
25
25
  } from './SDKSettings.js';
26
- import {SourceMap} from './SourceMap.js';
26
+ import {SourceMap, SourceMapProvenance} from './SourceMap.js';
27
27
  import {SourceMapManager} from './SourceMapManager.js';
28
28
  import {Capability, type Target} from './Target.js';
29
29
 
@@ -189,10 +189,10 @@ export class DebuggerModel extends SDKModel<EventTypes> {
189
189
  this.agent = target.debuggerAgent();
190
190
  this.#runtimeModel = (target.model(RuntimeModel) as RuntimeModel);
191
191
 
192
- this.#sourceMapManager =
193
- new SourceMapManager(target,
194
- (compiledURL, sourceMappingURL, payload, script) => new SourceMap(
195
- compiledURL, sourceMappingURL, payload, target.targetManager().getConsole(), script));
192
+ this.#sourceMapManager = new SourceMapManager(
193
+ target,
194
+ (compiledURL, sourceMappingURL, payload, script, provenance) => new SourceMap(
195
+ compiledURL, sourceMappingURL, payload, target.targetManager().getConsole(), script, provenance));
196
196
 
197
197
  const settings = this.target().targetManager().settings;
198
198
  this.#pauseOnExceptionEnabledSetting = settings.resolve(pauseOnExceptionEnabledSettingDescriptor);
@@ -719,7 +719,7 @@ export class DebuggerModel extends SDKModel<EventTypes> {
719
719
 
720
720
  if ((!selectedDebugSymbol || selectedDebugSymbol.type === Protocol.Debugger.DebugSymbolsType.SourceMap) &&
721
721
  script.sourceMapURL && !hasSyntaxError) {
722
- this.#sourceMapManager.attachSourceMap(script, script.sourceURL, script.sourceMapURL);
722
+ this.#sourceMapManager.attachSourceMap(script, script.sourceURL, script.sourceMapURL, SourceMapProvenance.CDP);
723
723
  }
724
724
 
725
725
  const isDiscardable = hasSyntaxError && script.isAnonymousScript();
@@ -730,11 +730,12 @@ export class DebuggerModel extends SDKModel<EventTypes> {
730
730
  return script;
731
731
  }
732
732
 
733
- setSourceMapURL(script: Script, newSourceMapURL: Platform.DevToolsPath.UrlString): void {
733
+ setSourceMapURL(script: Script, newSourceMapURL: Platform.DevToolsPath.UrlString,
734
+ provenance: SourceMapProvenance): void {
734
735
  // Detach any previous source map from the `script` first.
735
736
  this.#sourceMapManager.detachSourceMap(script);
736
737
  script.sourceMapURL = newSourceMapURL;
737
- this.#sourceMapManager.attachSourceMap(script, script.sourceURL, script.sourceMapURL);
738
+ this.#sourceMapManager.attachSourceMap(script, script.sourceURL, script.sourceMapURL, provenance);
738
739
  }
739
740
 
740
741
  async setDebugInfoURL(script: Script, _externalURL: Platform.DevToolsPath.UrlString): Promise<void> {
@@ -1566,7 +1566,7 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
1566
1566
  return this.#signedExchangeInfo;
1567
1567
  }
1568
1568
 
1569
- async populateImageSource(image: HTMLImageElement): Promise<void> {
1569
+ async populateImageSource(image: {src: string}): Promise<void> {
1570
1570
  const contentData = await this.requestContentData();
1571
1571
  if (TextUtils.ContentData.ContentData.isError(contentData)) {
1572
1572
  return;
@@ -0,0 +1,187 @@
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 type * as Protocol from '../../generated/protocol.js';
6
+
7
+ /**
8
+ * Functions in this file are serialized via `.toString()` and evaluated in the
9
+ * inspected page context over CDP (e.g. `Runtime.callFunctionOn`). Because they
10
+ * execute in the inspected browser environment, they use standard DOM types.
11
+ */
12
+
13
+ export function scrollListenerInPage(this: HTMLElement|Document, id: number, reportScrollPositionBindingName: string,
14
+ scrollListenerNameInPage: string): void {
15
+ if ('scrollingElement' in this && !this.scrollingElement) {
16
+ return;
17
+ }
18
+
19
+ const scrollingElement = ('scrollingElement' in this ? this.scrollingElement : this) as HTMLElement;
20
+ // @ts-expect-error We're setting a custom field on `Element` or `Document` for retaining the function on the page.
21
+ this[scrollListenerNameInPage] = () => {
22
+ // @ts-expect-error `reportScrollPosition` binding is injected to the page before calling the function.
23
+ globalThis[reportScrollPositionBindingName](
24
+ JSON.stringify({scrollTop: scrollingElement.scrollTop, scrollLeft: scrollingElement.scrollLeft, id}));
25
+ };
26
+
27
+ // @ts-expect-error We've already defined the function used below.
28
+ this.addEventListener('scroll', this[scrollListenerNameInPage], true);
29
+ }
30
+
31
+ export function removeScrollListenerInPage(this: HTMLElement|Document, scrollListenerNameInPage: string): void {
32
+ // @ts-expect-error We've already set this custom field while adding scroll listener.
33
+ this.removeEventListener('scroll', this[scrollListenerNameInPage]);
34
+ // @ts-expect-error We've already set this custom field while adding scroll listener.
35
+ delete this[scrollListenerNameInPage];
36
+ }
37
+
38
+ export function scrollTopInPage(this: Element|Document): number {
39
+ if ('scrollingElement' in this) {
40
+ if (!this.scrollingElement) {
41
+ return 0;
42
+ }
43
+
44
+ return this.scrollingElement.scrollTop;
45
+ }
46
+ return this.scrollTop;
47
+ }
48
+
49
+ export function scrollLeftInPage(this: Element|Document): number {
50
+ if ('scrollingElement' in this) {
51
+ if (!this.scrollingElement) {
52
+ return 0;
53
+ }
54
+
55
+ return this.scrollingElement.scrollLeft;
56
+ }
57
+ return this.scrollLeft;
58
+ }
59
+
60
+ export function setScrollTopInPage(this: Element|Document, offsetInPage: number): void {
61
+ if ('scrollingElement' in this) {
62
+ if (!this.scrollingElement) {
63
+ return;
64
+ }
65
+
66
+ this.scrollingElement.scrollTop = offsetInPage;
67
+ } else {
68
+ this.scrollTop = offsetInPage;
69
+ }
70
+ }
71
+
72
+ export function setScrollLeftInPage(this: Element|Document, offsetInPage: number): void {
73
+ if ('scrollingElement' in this) {
74
+ if (!this.scrollingElement) {
75
+ return;
76
+ }
77
+
78
+ this.scrollingElement.scrollLeft = offsetInPage;
79
+ } else {
80
+ this.scrollLeft = offsetInPage;
81
+ }
82
+ }
83
+
84
+ export function verticalScrollRangeInPage(this: Element|Document): number {
85
+ if ('scrollingElement' in this) {
86
+ if (!this.scrollingElement) {
87
+ return 0;
88
+ }
89
+
90
+ return this.scrollingElement.scrollHeight - this.scrollingElement.clientHeight;
91
+ }
92
+
93
+ return this.scrollHeight - this.clientHeight;
94
+ }
95
+
96
+ export function horizontalScrollRangeInPage(this: Element|Document): number {
97
+ if ('scrollingElement' in this) {
98
+ if (!this.scrollingElement) {
99
+ return 0;
100
+ }
101
+
102
+ return this.scrollingElement.scrollWidth - this.scrollingElement.clientWidth;
103
+ }
104
+
105
+ return this.scrollWidth - this.clientWidth;
106
+ }
107
+
108
+ export function toggleClassAndInjectStyleRule(this: Element, pseudoElementName: string|null, hidden: boolean): void {
109
+ const classNamePrefix = '__web-inspector-hide';
110
+ const classNameSuffix = '-shortcut__';
111
+ const styleTagId = '__web-inspector-hide-shortcut-style__';
112
+ const pseudoElementNameEscaped = pseudoElementName ? pseudoElementName.replace(/[\(\)\:]/g, '_') : '';
113
+ const className = classNamePrefix + pseudoElementNameEscaped + classNameSuffix;
114
+ this.classList.toggle(className, hidden);
115
+
116
+ let localRoot: Element|HTMLHeadElement = this;
117
+ while (localRoot.parentNode) {
118
+ localRoot = (localRoot.parentNode as Element);
119
+ }
120
+ if (localRoot.nodeType === Node.DOCUMENT_NODE) {
121
+ localRoot = document.head;
122
+ }
123
+
124
+ let style = localRoot.querySelector('style#' + styleTagId);
125
+ if (!style) {
126
+ const selectors = [];
127
+ selectors.push('.__web-inspector-hide-shortcut__');
128
+ selectors.push('.__web-inspector-hide-shortcut__ *');
129
+ const selector = selectors.join(', ');
130
+ const ruleBody = ' visibility: hidden !important;';
131
+ const rule = '\n' + selector + '\n{\n' + ruleBody + '\n}\n';
132
+
133
+ style = document.createElement('style');
134
+ style.id = styleTagId;
135
+ style.textContent = rule;
136
+
137
+ localRoot.appendChild(style);
138
+ }
139
+
140
+ // In addition to putting them on the element we want to hide, we will
141
+ // also add pseudo element classes to the style element to keep track of
142
+ // which pseudo elements we have style rules for.
143
+ if (pseudoElementName && !style.classList.contains(className)) {
144
+ style.classList.add(className);
145
+ style.textContent = `.${className}${pseudoElementName}, ${style.textContent}`;
146
+ }
147
+ }
148
+
149
+ export function scrollIntoViewInPage(this: Element): void {
150
+ this.scrollIntoViewIfNeeded(true);
151
+ }
152
+
153
+ export function focusInPage(this: HTMLElement): void {
154
+ this.focus();
155
+ }
156
+
157
+ export function toStringForClipboard(this: Object, data: {
158
+ subtype: string|undefined,
159
+ indent: string,
160
+ }): string|undefined {
161
+ const subtype = data.subtype;
162
+ const indent = data.indent;
163
+
164
+ if (subtype === 'node') {
165
+ return this instanceof Element ? this.outerHTML : undefined;
166
+ }
167
+ if (subtype && typeof this === 'undefined') {
168
+ return String(subtype);
169
+ }
170
+ try {
171
+ return JSON.stringify(this, null, indent);
172
+ } catch {
173
+ return String(this);
174
+ }
175
+ }
176
+
177
+ export function saveVariable(this: Window, value: Protocol.Runtime.CallArgument): string {
178
+ const prefix = 'temp';
179
+ let index = 1;
180
+ while ((prefix + index) in this) {
181
+ ++index;
182
+ }
183
+ const name = prefix + index;
184
+ // @ts-expect-error Assignment to global object
185
+ this[name] = value;
186
+ return name;
187
+ }
@@ -199,7 +199,7 @@ export class Resource implements TextUtils.ContentProvider.ContentProvider {
199
199
  return TextUtils.TextUtils.performSearchInSearchMatches(result.result || [], query, caseSensitive, isRegex);
200
200
  }
201
201
 
202
- async populateImageSource(image: HTMLImageElement): Promise<void> {
202
+ async populateImageSource(image: {src: string}): Promise<void> {
203
203
  const contentData = await this.requestContentData();
204
204
  if (TextUtils.ContentData.ContentData.isError(contentData)) {
205
205
  return;
@@ -10,6 +10,7 @@ import type * as Platform from '../platform/platform.js';
10
10
 
11
11
  import {DebuggerModel, type FunctionDetails} from './DebuggerModel.js';
12
12
  import {HeapProfilerModel} from './HeapProfilerModel.js';
13
+ import {toStringForClipboard} from './PageFunctions.js';
13
14
  import {
14
15
  RemoteFunction,
15
16
  RemoteObject,
@@ -313,26 +314,6 @@ export class RuntimeModel extends SDKModel<EventTypes> {
313
314
  }])
314
315
  .then(Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText.bind(
315
316
  Host.InspectorFrontendHost.InspectorFrontendHostInstance));
316
-
317
- function toStringForClipboard(this: Object, data: {
318
- subtype: string,
319
- indent: string,
320
- }): string|undefined {
321
- const subtype = data.subtype;
322
- const indent = data.indent;
323
-
324
- if (subtype === 'node') {
325
- return this instanceof Element ? this.outerHTML : undefined;
326
- }
327
- if (subtype && typeof this === 'undefined') {
328
- return String(subtype);
329
- }
330
- try {
331
- return JSON.stringify(this, null, indent);
332
- } catch {
333
- return String(this);
334
- }
335
- }
336
317
  }
337
318
 
338
319
  private async queryObjectsRequested(object: RemoteObject, executionContextId?: number): Promise<void> {
@@ -43,6 +43,7 @@ import type {FrameAssociated} from './FrameAssociated.js';
43
43
  import type {PageResourceLoadInitiator} from './PageResourceLoader.js';
44
44
  import {ResourceTreeModel} from './ResourceTreeModel.js';
45
45
  import type {ExecutionContext} from './RuntimeModel.js';
46
+ import {SecurityOrigin} from './SecurityOrigin.js';
46
47
  import type {DebugId, SourceMap} from './SourceMap.js';
47
48
  import type {Target} from './Target.js';
48
49
 
@@ -88,6 +89,7 @@ export class Script implements TextUtils.ContentProvider.ContentProvider, FrameA
88
89
  readonly #language: string|null;
89
90
  #contentPromise: Promise<TextUtils.ContentData.ContentDataOrError>|null;
90
91
  readonly #embedderName: Platform.DevToolsPath.UrlString|null;
92
+ readonly #securityOrigin: SecurityOrigin;
91
93
  readonly isModule: boolean|null;
92
94
  readonly buildId: string|null;
93
95
  constructor(debuggerModel: DebuggerModel, scriptId: Protocol.Runtime.ScriptId,
@@ -119,12 +121,31 @@ export class Script implements TextUtils.ContentProvider.ContentProvider, FrameA
119
121
  this.#language = scriptLanguage;
120
122
  this.#contentPromise = null;
121
123
  this.#embedderName = embedderName;
124
+ this.#securityOrigin = SecurityOrigin.create(this.#embedderName ?? '');
122
125
  }
123
126
 
124
127
  embedderName(): Platform.DevToolsPath.UrlString|null {
125
128
  return this.#embedderName;
126
129
  }
127
130
 
131
+ /**
132
+ * Returns the security origin of the script derived exclusively from its
133
+ * embedder/network URL (`#embedderName`), or a unique opaque origin if the
134
+ * script has no valid network provenance (e.g. `eval()` or buffer-based Wasm).
135
+ *
136
+ * Security note: Do NOT fall back to `this.sourceURL` or
137
+ * `this.target().inspectedSecurityOrigin()`:
138
+ * - `sourceURL` is overwritten by `//# sourceURL=` comments, allowing a script
139
+ * to spoof an arbitrary origin.
140
+ * - Third-party scripts (`<script src="https://attacker.example/...">`) run in
141
+ * the same target/frame as the main page; falling back to the target's
142
+ * origin would allow them to launder their origin by dynamically evaluating
143
+ * code via `eval()` or `WebAssembly.instantiate(buffer)`.
144
+ */
145
+ securityOrigin(): SecurityOrigin {
146
+ return this.#securityOrigin;
147
+ }
148
+
128
149
  target(): Target {
129
150
  return this.debuggerModel.target();
130
151
  }
@@ -130,6 +130,12 @@ interface SourceInfo {
130
130
  reverseMappings: number[]|null;
131
131
  }
132
132
 
133
+ export const enum SourceMapProvenance {
134
+ CDP = 'cdp',
135
+ EXTENSION = 'extension',
136
+ USER = 'user',
137
+ }
138
+
133
139
  export class SourceMap {
134
140
  static retainRawSourceMaps = false;
135
141
 
@@ -149,13 +155,15 @@ export class SourceMap {
149
155
 
150
156
  #scopesFallbackPromise?: Promise<void>;
151
157
  readonly #console: Common.Console.Console;
158
+ readonly #provenance: SourceMapProvenance;
152
159
 
153
160
  /**
154
161
  * Implements Source Map V3 model. See https://github.com/google/closure-compiler/wiki/Source-Maps
155
162
  * for format description.
156
163
  */
157
164
  constructor(compiledURL: Platform.DevToolsPath.UrlString, sourceMappingURL: Platform.DevToolsPath.UrlString,
158
- payload: SourceMapV3, console: Common.Console.Console, script?: Script) {
165
+ payload: SourceMapV3, console: Common.Console.Console, script?: Script,
166
+ provenance: SourceMapProvenance = SourceMapProvenance.CDP) {
159
167
  this.#json = payload;
160
168
  this.#script = script;
161
169
  this.#compiledURL = compiledURL;
@@ -163,6 +171,7 @@ export class SourceMap {
163
171
  this.#baseURL = (Common.ParsedURL.schemeIs(sourceMappingURL, 'data:')) ? compiledURL : sourceMappingURL;
164
172
  this.#debugId = 'debugId' in payload ? (payload.debugId as DebugId | undefined) : undefined;
165
173
  this.#console = console;
174
+ this.#provenance = provenance;
166
175
 
167
176
  if ('sections' in this.#json) {
168
177
  if (this.#json.sections.find(section => 'url' in section)) {
@@ -172,6 +181,10 @@ export class SourceMap {
172
181
  this.eachSection(this.parseSources.bind(this));
173
182
  }
174
183
 
184
+ provenance(): SourceMapProvenance {
185
+ return this.#provenance;
186
+ }
187
+
175
188
  json(): SourceMapV3|null {
176
189
  return this.#json;
177
190
  }
@@ -823,7 +836,7 @@ export class SourceMap {
823
836
 
824
837
  resolveScopeChain(frame: CallFrame): ScopeChainEntry[]|null {
825
838
  this.#ensureSourceMapProcessed();
826
- if (this.#scopesInfo === null) {
839
+ if (this.#provenance === SourceMapProvenance.USER || !this.#scopesInfo?.hasVariablesAndBindings()) {
827
840
  return null;
828
841
  }
829
842
 
@@ -2,15 +2,14 @@
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 Platform from '../platform/platform.js';
5
+ import * as Platform from '../platform/platform.js';
6
6
 
7
7
  import type {DebugId, SourceMapV3} from './SourceMap.js';
8
8
 
9
9
  /** A thin wrapper around the Cache API to store source map JSONs keyed on Debug IDs */
10
10
  export class SourceMapCache {
11
11
  static create(): SourceMapCache {
12
- if (typeof window === 'undefined') {
13
- // TODO(crbug.com/451502260): Move this behind a `HostRuntime` interface.
12
+ if (!Platform.HostRuntime.HOST_RUNTIME.getCacheStorage()) {
14
13
  return IN_MEMORY_INSTANCE as unknown as
15
14
  SourceMapCache; // TS doesn't like that our in-memory class doesn't have the same private fields.
16
15
  }
@@ -22,7 +21,7 @@ export class SourceMapCache {
22
21
  }
23
22
 
24
23
  readonly #name: string;
25
- #cachePromise?: Promise<Cache>;
24
+ #cachePromise?: Promise<Platform.HostRuntime.CacheEntry>;
26
25
 
27
26
  private constructor(name: string) {
28
27
  this.#name = name;
@@ -30,21 +29,25 @@ export class SourceMapCache {
30
29
 
31
30
  async set(debugId: DebugId, securityOrigin: Platform.DevToolsPath.UrlString, sourceMap: SourceMapV3): Promise<void> {
32
31
  const cache = await this.#cache();
33
- await cache.put(SourceMapCache.#urlForDebugId(debugId, securityOrigin), new Response(JSON.stringify(sourceMap)));
32
+ await cache?.put(SourceMapCache.#urlForDebugId(debugId, securityOrigin), new Response(JSON.stringify(sourceMap)));
34
33
  }
35
34
 
36
35
  async get(debugId: DebugId, securityOrigin: Platform.DevToolsPath.UrlString): Promise<SourceMapV3|null> {
37
36
  const cache = await this.#cache();
38
- const response = await cache.match(SourceMapCache.#urlForDebugId(debugId, securityOrigin));
39
- return await response?.json() ?? null;
37
+ const response = await cache?.match(SourceMapCache.#urlForDebugId(debugId, securityOrigin));
38
+ return (await response?.json() as SourceMapV3 | undefined) ?? null;
40
39
  }
41
40
 
42
- async #cache(): Promise<Cache> {
41
+ async #cache(): Promise<Platform.HostRuntime.CacheEntry|undefined> {
43
42
  if (this.#cachePromise) {
44
43
  return await this.#cachePromise;
45
44
  }
46
45
 
47
- this.#cachePromise = window.caches.open(this.#name);
46
+ const cacheStorage = Platform.HostRuntime.HOST_RUNTIME.getCacheStorage();
47
+ if (!cacheStorage) {
48
+ return undefined;
49
+ }
50
+ this.#cachePromise = cacheStorage.open(this.#name);
48
51
  return await this.#cachePromise;
49
52
  }
50
53
 
@@ -54,7 +57,7 @@ export class SourceMapCache {
54
57
  }
55
58
 
56
59
  async disposeForTest(): Promise<void> {
57
- await window.caches.delete(this.#name);
60
+ await Platform.HostRuntime.HOST_RUNTIME.getCacheStorage()?.delete(this.#name);
58
61
  }
59
62
  }
60
63
 
@@ -7,13 +7,13 @@ import * as Platform from '../platform/platform.js';
7
7
 
8
8
  import type {FrameAssociated} from './FrameAssociated.js';
9
9
  import {PageResourceLoader, type PageResourceLoadInitiator, type ResourceLoader} from './PageResourceLoader.js';
10
- import {type DebugId, parseSourceMap, SourceMap, type SourceMapV3} from './SourceMap.js';
10
+ import {type DebugId, parseSourceMap, SourceMap, type SourceMapProvenance, type SourceMapV3} from './SourceMap.js';
11
11
  import {SourceMapCache} from './SourceMapCache.js';
12
12
  import {type Target, Type} from './Target.js';
13
13
 
14
14
  export type SourceMapFactory<T> =
15
15
  (compiledURL: Platform.DevToolsPath.UrlString, sourceMappingURL: Platform.DevToolsPath.UrlString,
16
- payload: SourceMapV3, client: T) => SourceMap;
16
+ payload: SourceMapV3, client: T, provenance: SourceMapProvenance) => SourceMap;
17
17
 
18
18
  export const lazyLoadingSettingDescriptor: Common.Settings.SettingDescriptor<boolean> = {
19
19
  name: 'source-maps-lazy-loading',
@@ -37,8 +37,8 @@ export class SourceMapManager<T extends FrameAssociated> extends Common.ObjectWr
37
37
 
38
38
  this.#target = target;
39
39
  this.#factory = factory ??
40
- ((compiledURL, sourceMappingURL, payload) =>
41
- new SourceMap(compiledURL, sourceMappingURL, payload, this.#target.targetManager().getConsole()));
40
+ ((compiledURL, sourceMappingURL, payload, _client, provenance) => new SourceMap(
41
+ compiledURL, sourceMappingURL, payload, this.#target.targetManager().getConsole(), undefined, provenance));
42
42
  const settings = target.targetManager().settings;
43
43
  this.#lazyLoadingSetting = settings.resolve(lazyLoadingSettingDescriptor);
44
44
  }
@@ -60,8 +60,8 @@ export class SourceMapManager<T extends FrameAssociated> extends Common.ObjectWr
60
60
  this.detachSourceMap(client);
61
61
  }
62
62
  this.#isEnabled = isEnabled;
63
- for (const [client, {relativeSourceURL, relativeSourceMapURL}] of clientData) {
64
- this.attachSourceMap(client, relativeSourceURL, relativeSourceMapURL);
63
+ for (const [client, {relativeSourceURL, relativeSourceMapURL, provenance}] of clientData) {
64
+ this.attachSourceMap(client, relativeSourceURL, relativeSourceMapURL, provenance);
65
65
  }
66
66
  }
67
67
 
@@ -97,8 +97,8 @@ export class SourceMapManager<T extends FrameAssociated> extends Common.ObjectWr
97
97
  }
98
98
 
99
99
  // TODO(bmeurer): We are lying about the type of |relativeSourceURL| here.
100
- attachSourceMap(
101
- client: T, relativeSourceURL: Platform.DevToolsPath.UrlString, relativeSourceMapURL: string|undefined): void {
100
+ attachSourceMap(client: T, relativeSourceURL: Platform.DevToolsPath.UrlString, relativeSourceMapURL: string|undefined,
101
+ provenance: SourceMapProvenance): void {
102
102
  if (this.#clientData.has(client)) {
103
103
  throw new Error('SourceMap is already attached or being attached to client');
104
104
  }
@@ -109,6 +109,7 @@ export class SourceMapManager<T extends FrameAssociated> extends Common.ObjectWr
109
109
  const clientData: ClientData = {
110
110
  relativeSourceURL,
111
111
  relativeSourceMapURL,
112
+ provenance,
112
113
  getSourceMap: () => Promise.resolve(undefined),
113
114
  };
114
115
  this.#clientData.set(client, clientData);
@@ -142,7 +143,7 @@ export class SourceMapManager<T extends FrameAssociated> extends Common.ObjectWr
142
143
  loadSourceMap(resourceLoader, this.#sourceMapCache, sourceMapURL, client.debugId(), initiator)
143
144
  .then(
144
145
  payload => {
145
- const sourceMap = this.#factory(sourceURL, sourceMapURL, payload, client);
146
+ const sourceMap = this.#factory(sourceURL, sourceMapURL, payload, client, provenance);
146
147
  if (this.#clientData.get(client) === clientData) {
147
148
  clientData.sourceMap = sourceMap;
148
149
  this.#sourceMaps.set(sourceMap, client);
@@ -257,6 +258,7 @@ interface ClientData {
257
258
  // Stores the raw sourceMappingURL as provided by V8. These are not guaranteed to
258
259
  // be valid URLs and will be checked and resolved once `attachSourceMap` is called.
259
260
  relativeSourceMapURL: string;
261
+ provenance: SourceMapProvenance;
260
262
  sourceMap?: SourceMap;
261
263
  getSourceMap: () => Promise<SourceMap|undefined>;
262
264
  }
@@ -27,6 +27,10 @@ const UIStrings = {
27
27
  * @description Title of a section in the debugger showing JavaScript variables from the global scope.
28
28
  */
29
29
  global: 'Global',
30
+ /**
31
+ * @description Text in Scope Chain section of the Sources panel.
32
+ */
33
+ exception: 'Exception',
30
34
  /**
31
35
  * @description Text in Scope Chain section of the Sources panel.
32
36
  */
@@ -60,12 +64,18 @@ export class SourceMapScopeChainEntry implements ScopeChainEntry {
60
64
  }
61
65
 
62
66
  extraProperties(): RemoteObjectProperty[] {
67
+ const extraProperties = [];
68
+ if (this.#isInnerMostFunction && this.#callFrame.exception) {
69
+ extraProperties.push(new RemoteObjectProperty(i18nString(UIStrings.exception), this.#callFrame.exception,
70
+ undefined, undefined, undefined, undefined, undefined,
71
+ /* synthetic */ true));
72
+ }
63
73
  if (this.#returnValue) {
64
- return [new RemoteObjectProperty(
74
+ extraProperties.push(new RemoteObjectProperty(
65
75
  i18nString(UIStrings.returnValue), this.#returnValue, undefined, undefined, undefined, undefined, undefined,
66
- /* synthetic */ true)];
76
+ /* synthetic */ true, this.#callFrame.setReturnValue.bind(this.#callFrame)));
67
77
  }
68
- return [];
78
+ return extraProperties;
69
79
  }
70
80
 
71
81
  callFrame(): CallFrame {
@@ -144,23 +154,55 @@ class SourceMapScopeRemoteObject extends RemoteObjectImpl {
144
154
  return {properties: [], internalProperties: []};
145
155
  }
146
156
 
147
- const properties: RemoteObjectProperty[] = [];
148
- for (const [index, variable] of this.#scope.variables.entries()) {
149
- const expression = this.#findExpression(index);
150
- if (expression === null) {
151
- properties.push(SourceMapScopeRemoteObject.#unavailableProperty(variable));
152
- continue;
157
+ if (this.#scope.variables.length === 0) {
158
+ return {properties: [], internalProperties: []};
159
+ }
160
+
161
+ const expressions = this.#scope.variables.map((_, index) => this.#findExpression(index));
162
+
163
+ if (expressions.every(expr => expr === null)) {
164
+ const properties = this.#scope.variables.map(v => SourceMapScopeRemoteObject.#unavailableProperty(v));
165
+ return {properties, internalProperties: []};
166
+ }
167
+
168
+ const spreadEntries: string[] = [];
169
+ for (const [index, expr] of expressions.entries()) {
170
+ if (expr !== null) {
171
+ spreadEntries.push(`...(() => { try { return {${index}: eval(${JSON.stringify(expr)})}; } catch {} })()`);
153
172
  }
173
+ }
174
+
175
+ const batchExpression = `({ __proto__: null, ${spreadEntries.join(', ')} })`;
176
+ const result = await this.#callFrame.evaluate({
177
+ expression: batchExpression,
178
+ generatePreview: false,
179
+ scopeNumber: this.#scopeNumber,
180
+ });
181
+
182
+ if ('error' in result || result.exceptionDetails || !result.object) {
183
+ const properties = this.#scope.variables.map(v => SourceMapScopeRemoteObject.#unavailableProperty(v));
184
+ return {properties, internalProperties: []};
185
+ }
186
+
187
+ const {properties: objectProperties} = await result.object.getOwnProperties(generatePreview);
188
+ result.object.release();
154
189
 
155
- const result = await this.#callFrame.evaluate({expression, generatePreview, scopeNumber: this.#scopeNumber});
156
- if ('error' in result || result.exceptionDetails) {
157
- // TODO(crbug.com/40277685): Make these errors user-visible to aid tooling developers.
158
- // E.g. show the error on hover or expose it in the developer resources panel.
190
+ const propertyMap = new Map<string, RemoteObjectProperty>();
191
+ if (objectProperties) {
192
+ for (const prop of objectProperties) {
193
+ propertyMap.set(prop.name, prop);
194
+ }
195
+ }
196
+
197
+ const properties: RemoteObjectProperty[] = [];
198
+ for (const [index, variable] of this.#scope.variables.entries()) {
199
+ const prop = propertyMap.get(String(index));
200
+ if (!prop || !prop.value) {
159
201
  properties.push(SourceMapScopeRemoteObject.#unavailableProperty(variable));
160
202
  } else {
161
- properties.push(new RemoteObjectProperty(
162
- variable, result.object, /* enumerable */ false, /* writable */ false, /* isOwn */ true,
163
- /* wasThrown */ false));
203
+ properties.push(new RemoteObjectProperty(variable, prop.value, /* enumerable */ false, /* writable */ false,
204
+ /* isOwn */ true,
205
+ /* wasThrown */ false));
164
206
  }
165
207
  }
166
208
 
@@ -177,7 +219,7 @@ class SourceMapScopeRemoteObject extends RemoteObjectImpl {
177
219
  if (typeof expressionOrSubRanges === 'string') {
178
220
  return expressionOrSubRanges;
179
221
  }
180
- if (expressionOrSubRanges === null) {
222
+ if (expressionOrSubRanges === null || expressionOrSubRanges === undefined) {
181
223
  return null;
182
224
  }
183
225
 
@@ -355,9 +355,10 @@ export class SourceMapScopesInfo {
355
355
  // w.r.t. each other.
356
356
 
357
357
  let seenFunctionScope = false;
358
- const result: SourceMapScopeChainEntry[] = [];
358
+ const result: ScopeChainEntry[] = [];
359
359
  // Walk the original scope chain outwards and try to find the corresponding generated range along the way.
360
- for (let originalScope = rangeChain.at(-1)?.originalScope; originalScope; originalScope = originalScope.parent) {
360
+ for (let originalScope: ScopesCodec.OriginalScope|null|undefined = innerMostOriginalScope; originalScope;
361
+ originalScope = originalScope.parent) {
361
362
  const range = rangeChain.findLast(r => r.originalScope === originalScope);
362
363
  // `kind` is just a label for scope UI views and has no semantic significance, so `isStackFrame`
363
364
  // decides whether this scope is a function scope.
@@ -370,6 +371,11 @@ export class SourceMapScopesInfo {
370
371
  seenFunctionScope ||= isFunctionScope;
371
372
  }
372
373
 
374
+ const globalScope = callFrame.scopeChain()?.find(s => s.type() === Protocol.Debugger.ScopeType.Global);
375
+ if (globalScope) {
376
+ result.push(globalScope);
377
+ }
378
+
373
379
  // If we are paused on a return statement, we need to drop inner block scopes. This is because V8 only emits a
374
380
  // single return bytecode and "gotos" at the functions' end, where we are now paused.
375
381
  if (callFrame.returnValue() !== null) {