chrome-devtools-frontend 1.0.1704172 → 1.0.1705227
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.
- package/AUTHORS +1 -0
- package/docs/architecture_of_devtools.md +1 -1
- package/docs/cookbook/localization.md +2 -2
- package/front_end/core/common/README.md +4 -3
- package/front_end/entrypoints/formatter_actions/formatter_actions.ts +18 -0
- package/front_end/entrypoints/formatter_worker/Substitute.ts +9 -8
- package/front_end/entrypoints/formatter_worker/formatter_worker-entrypoint.ts +2 -2
- package/front_end/foundation/Universe.ts +9 -0
- package/front_end/generated/InspectorBackendCommands.ts +3 -2
- package/front_end/generated/protocol.ts +16 -0
- package/front_end/models/formatter/FormatterWorkerPool.ts +8 -3
- package/front_end/models/source_map_scopes/NamesResolver.ts +62 -33
- package/front_end/models/source_map_scopes/ScopeChainModel.ts +13 -45
- package/front_end/models/source_map_scopes/ScopeChainResolver.ts +101 -0
- package/front_end/models/source_map_scopes/source_map_scopes.ts +2 -0
- package/front_end/panels/console/ConsolePrompt.ts +2 -1
- package/front_end/panels/sources/DebuggerPlugin.ts +3 -4
- package/front_end/panels/sources/ScopeChainSidebarPane.ts +2 -2
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/legacy/README.md +12 -12
- package/package.json +1 -1
package/AUTHORS
CHANGED
|
@@ -124,6 +124,7 @@ Yoichiro Hibara <hibarayoichiro871@gmail.com>
|
|
|
124
124
|
Yoni Feigelson <yfeigelson@gmail.com>
|
|
125
125
|
Young Min Kim <y@ylem.kim>
|
|
126
126
|
Yuan Zhuang <joann.yuanzhuang@gmail.com>
|
|
127
|
+
Zeeshan Ahmad <zeeshanahmad125114@gmail.com>
|
|
127
128
|
Zuyao Chen <zuyaocat@gmail.com>
|
|
128
129
|
# Please DO NOT APPEND here. See comments at the top of the file.
|
|
129
130
|
# END individuals section.
|
|
@@ -121,7 +121,7 @@ There are multiple variants of the DevTools application.
|
|
|
121
121
|
The main DevTools application is [devtools_app.ts](../front_end/entrypoints/devtools_app/devtools_app.ts), which developers can open via F12 or "Right click -> Inspect element" in their Chromium browser.
|
|
122
122
|
Other application entrypoints are a dedicated tool for debugging Node on which multiple targets can be inspected ([node_app.ts](../front_end/entrypoints/node_app/node_app.ts)), a non-browser context debugger for V8 targets ([js_app.ts](../front_end/entrypoints/js_app/js_app.ts)), a (service) worker context ([worker_app.ts](../front_end/entrypoints/worker_app/worker_app.ts)), remote debugging of (Android) devices ([inspector.ts](../front_end/entrypoints/inspector/inspector.ts)) and a basic version of DevTools which was initially intended for non-browser targets but doesn't have any particular use case that differentiates it from the other variants ([ndb_app.ts](../front_end/entrypoints/ndb_app/ndb_app.ts)).
|
|
123
123
|
|
|
124
|
-
Each application entrypoint has a corresponding `.html` file with the same name, that is automatically in the frontend
|
|
124
|
+
Each application entrypoint has a corresponding `.html` file with the same name, that is automatically generated in the frontend and can be loaded by the Chromium DevTools backend.
|
|
125
125
|
The JavaScript files import the relevant "meta" files containing the declarations of DevTools features.
|
|
126
126
|
|
|
127
127
|
## Upfront declaration of DevTools features
|
|
@@ -286,8 +286,8 @@ const UIStrings = {
|
|
|
286
286
|
|
|
287
287
|
## How to prevent a term being localized
|
|
288
288
|
|
|
289
|
-
Any text within the backticks will not be translated. For example,
|
|
290
|
-
'robots.txt' in string 'Requesting for robots.txt …' should not be translated:
|
|
289
|
+
Any text within the backticks will not be translated. For example,
|
|
290
|
+
'robots.txt' in the string 'Requesting for robots.txt …' should not be translated:
|
|
291
291
|
|
|
292
292
|
```ts
|
|
293
293
|
// in example.js file
|
|
@@ -6,14 +6,15 @@ A settings registration is represented by the `SettingRegistration` interface, d
|
|
|
6
6
|
|
|
7
7
|
All settings have to be registered using the function `Common.Settings.registerSettingExtension` which expects an object of type `SettingRegistration` as parameter.
|
|
8
8
|
|
|
9
|
-
As an example, take the registration of the `
|
|
9
|
+
As an example, take the registration of the `show-html-comments` setting, which allows users to determine if HTML comments are shown in the Elements tree:
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
Common.Settings.registerSettingExtension({
|
|
13
13
|
category: Common.Settings.SettingCategory.ELEMENTS,
|
|
14
|
+
storageType: Common.Settings.SettingStorageType.SYNCED,
|
|
14
15
|
order: 3,
|
|
15
|
-
title: ls`
|
|
16
|
-
settingName: '
|
|
16
|
+
title: ls`HTML comments`,
|
|
17
|
+
settingName: 'show-html-comments',
|
|
17
18
|
settingType: Common.Settings.SettingType.BOOLEAN,
|
|
18
19
|
defaultValue: true,
|
|
19
20
|
options: [
|
|
@@ -51,6 +51,24 @@ export const enum ScopeKind {
|
|
|
51
51
|
ARROW_FUNCTION = 4,
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Describes a single scope for `javaScriptSubstitute`.
|
|
56
|
+
*/
|
|
57
|
+
export interface ScopeVariableMapping {
|
|
58
|
+
/**
|
|
59
|
+
* Authored variable name -> generated binding expression, or `null` if the variable is unavailable.
|
|
60
|
+
*/
|
|
61
|
+
bindings: Map<string, string|null>;
|
|
62
|
+
/**
|
|
63
|
+
* Generated identifiers declared by this scope. Together with the free identifiers of `bindings`, these
|
|
64
|
+
* shadow bindings of outer scopes that refer to the same generated identifiers.
|
|
65
|
+
*
|
|
66
|
+
* This is needed when a generated identifier doesn't show up in any binding expression, e.g. because
|
|
67
|
+
* multiple generated identifiers map to the same authored name, or the authored name is unknown.
|
|
68
|
+
*/
|
|
69
|
+
generatedNames: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
export interface ScopeTreeNode {
|
|
55
73
|
variables: Array<{name: string, kind: DefinitionKind, offsets: number[]}>;
|
|
56
74
|
start: number;
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import * as Acorn from '../../third_party/acorn/acorn.js';
|
|
6
|
-
import {DefinitionKind} from '../formatter_actions/formatter_actions.js';
|
|
6
|
+
import {DefinitionKind, type ScopeVariableMapping} from '../formatter_actions/formatter_actions.js';
|
|
7
7
|
|
|
8
8
|
import {ECMA_VERSION} from './AcornTokenizer.js';
|
|
9
9
|
import {ScopeVariableAnalysis, type VariableUses} from './ScopeParser.js';
|
|
10
10
|
|
|
11
|
-
export function substituteExpression(expression: string,
|
|
12
|
-
const replacements = computeSubstitution(expression,
|
|
11
|
+
export function substituteExpression(expression: string, scopes: ScopeVariableMapping[]): string {
|
|
12
|
+
const replacements = computeSubstitution(expression, scopes);
|
|
13
13
|
return applySubstitution(expression, replacements);
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -52,12 +52,12 @@ function parseBindingExpression(expression: string): {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Given an |expression| and a
|
|
55
|
+
* Given an |expression| and a list of |scopes| (from inner-most to outer-most), the |computeSubstitution|
|
|
56
56
|
* function returns a list of replacements sorted by the offset. The function throws if
|
|
57
57
|
* it cannot parse the expression or the substitution is impossible to perform (for example
|
|
58
58
|
* if the substitution target is 'this' within a function, it would become bound there).
|
|
59
59
|
**/
|
|
60
|
-
function computeSubstitution(expression: string,
|
|
60
|
+
function computeSubstitution(expression: string, scopes: ScopeVariableMapping[]): Replacement[] {
|
|
61
61
|
// Parse the expression and find variables and scopes.
|
|
62
62
|
const root = Acorn.parse(expression, {
|
|
63
63
|
ecmaVersion: ECMA_VERSION,
|
|
@@ -75,10 +75,11 @@ function computeSubstitution(expression: string, nameMaps: Array<Map<string, str
|
|
|
75
75
|
const allNames = scopeVariables.getAllNames();
|
|
76
76
|
const nameMap = new Map<string, string|null>();
|
|
77
77
|
const parsedBindings = new Map<string, ReturnType<typeof parseBindingExpression>>();
|
|
78
|
+
// Generated identifiers declared by the scopes visited so far. Outer bindings referring to them are shadowed.
|
|
78
79
|
const shadowedNames = new Set<string>();
|
|
79
|
-
for (const
|
|
80
|
-
const scopeNames = new Set<string>();
|
|
81
|
-
for (const [name, rename] of
|
|
80
|
+
for (const {bindings, generatedNames} of scopes) {
|
|
81
|
+
const scopeNames = new Set<string>(generatedNames);
|
|
82
|
+
for (const [name, rename] of bindings.entries()) {
|
|
82
83
|
let parsed: ReturnType<typeof parseBindingExpression>|undefined;
|
|
83
84
|
if (rename !== null) {
|
|
84
85
|
try {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import * as Platform from '../../core/platform/platform.js';
|
|
6
|
-
import {FormatterActions} from '../formatter_actions/formatter_actions.js';
|
|
6
|
+
import {FormatterActions, type ScopeVariableMapping} from '../formatter_actions/formatter_actions.js';
|
|
7
7
|
|
|
8
8
|
import * as FormatterWorker from './formatter_worker.js';
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@ Platform.HostRuntime.HOST_RUNTIME.workerScope.onmessage = function(event): void
|
|
|
13
13
|
indentString: string,
|
|
14
14
|
content: string,
|
|
15
15
|
mimeType: string,
|
|
16
|
-
mapping:
|
|
16
|
+
mapping: ScopeVariableMapping[],
|
|
17
17
|
sourceType: 'module'|'script',
|
|
18
18
|
} = event.data.params;
|
|
19
19
|
if (!method) {
|
|
@@ -21,6 +21,7 @@ import * as LiveMetrics from '../models/live-metrics/live-metrics.js';
|
|
|
21
21
|
import * as Logs from '../models/logs/logs.js';
|
|
22
22
|
import * as Persistence from '../models/persistence/persistence.js';
|
|
23
23
|
import * as ProjectSettings from '../models/project_settings/project_settings.js';
|
|
24
|
+
import * as SourceMapScopes from '../models/source_map_scopes/source_map_scopes.js';
|
|
24
25
|
import * as Workspace from '../models/workspace/workspace.js';
|
|
25
26
|
import * as WorkspaceDiff from '../models/workspace_diff/workspace_diff.js';
|
|
26
27
|
|
|
@@ -160,6 +161,10 @@ export class Universe {
|
|
|
160
161
|
resourceMapping, targetManager, ignoreListManager, workspace);
|
|
161
162
|
context.set(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, debuggerWorkspaceBinding);
|
|
162
163
|
|
|
164
|
+
const scopeChainResolver =
|
|
165
|
+
new SourceMapScopes.ScopeChainResolver.ScopeChainResolver(targetManager, debuggerWorkspaceBinding);
|
|
166
|
+
context.set(SourceMapScopes.ScopeChainResolver.ScopeChainResolver, scopeChainResolver);
|
|
167
|
+
|
|
163
168
|
const presentationConsoleMessageManager =
|
|
164
169
|
new Bindings.PresentationConsoleMessageHelper.PresentationConsoleMessageManager(
|
|
165
170
|
targetManager, workspace, debuggerWorkspaceBinding, cssWorkspaceBinding);
|
|
@@ -383,6 +388,10 @@ export class Universe {
|
|
|
383
388
|
null;
|
|
384
389
|
}
|
|
385
390
|
|
|
391
|
+
get scopeChainResolver(): SourceMapScopes.ScopeChainResolver.ScopeChainResolver {
|
|
392
|
+
return this.context.get(SourceMapScopes.ScopeChainResolver.ScopeChainResolver);
|
|
393
|
+
}
|
|
394
|
+
|
|
386
395
|
get settings(): Common.Settings.Settings {
|
|
387
396
|
return this.context.get(Common.Settings.Settings);
|
|
388
397
|
}
|
|
@@ -545,7 +545,8 @@ inspectorBackend.registerCommand("Emulation.setSafeAreaInsetsOverride", [{"name"
|
|
|
545
545
|
inspectorBackend.registerCommand("Emulation.setVirtualKeyboardGeometryOverride", [{"name": "keyboardRect", "type": "object", "optional": true, "description": "", "typeRef": "DOM.Rect"}], [], "Overrides virtual keyboard geometry in CSS pixels, relative to the top-level viewport. The provided rect is used for navigator.virtualKeyboard.boundingRect, geometrychange events, and env(keyboard-inset-*) values on the inspected frame. The override applies independently of navigator.virtualKeyboard.overlaysContent so clients can preview overlay geometry without mutating page state. Values are rounded to the nearest CSS pixel. Omitting the rect clears the override.");
|
|
546
546
|
inspectorBackend.registerEnum("Emulation.SetDeviceMetricsOverrideRequestScrollbarType", {Overlay: "overlay", Default: "default"});
|
|
547
547
|
inspectorBackend.registerEnum("Emulation.SetDeviceMetricsOverrideRequestViewportMeta", {Enable: "enable", Default: "default"});
|
|
548
|
-
inspectorBackend.
|
|
548
|
+
inspectorBackend.registerEnum("Emulation.SetDeviceMetricsOverrideRequestTextLayoutMode", {Mobile: "mobile", Default: "default"});
|
|
549
|
+
inspectorBackend.registerCommand("Emulation.setDeviceMetricsOverride", [{"name": "width", "type": "number", "optional": false, "description": "Overriding width value in pixels (minimum 0, maximum 10000000). 0 disables the override.", "typeRef": null}, {"name": "height", "type": "number", "optional": false, "description": "Overriding height value in pixels (minimum 0, maximum 10000000). 0 disables the override.", "typeRef": null}, {"name": "deviceScaleFactor", "type": "number", "optional": false, "description": "Overriding device scale factor value. 0 disables the override.", "typeRef": null}, {"name": "mobile", "type": "boolean", "optional": false, "description": "Whether to emulate mobile device. This includes viewport meta tag, overlay scrollbars, text autosizing and more.", "typeRef": null}, {"name": "scale", "type": "number", "optional": true, "description": "Scale to apply to resulting view image.", "typeRef": null}, {"name": "screenWidth", "type": "number", "optional": true, "description": "Overriding screen width value in pixels (minimum 0, maximum 10000000).", "typeRef": null}, {"name": "screenHeight", "type": "number", "optional": true, "description": "Overriding screen height value in pixels (minimum 0, maximum 10000000).", "typeRef": null}, {"name": "positionX", "type": "number", "optional": true, "description": "Overriding view X position on screen in pixels (minimum 0, maximum 10000000).", "typeRef": null}, {"name": "positionY", "type": "number", "optional": true, "description": "Overriding view Y position on screen in pixels (minimum 0, maximum 10000000).", "typeRef": null}, {"name": "dontSetVisibleSize", "type": "boolean", "optional": true, "description": "Do not set visible view size, rely upon explicit setVisibleSize call.", "typeRef": null}, {"name": "screenOrientation", "type": "object", "optional": true, "description": "Screen orientation override.", "typeRef": "Emulation.ScreenOrientation"}, {"name": "viewport", "type": "object", "optional": true, "description": "If set, the visible area of the page will be overridden to this viewport. This viewport change is not observed by the page, e.g. viewport-relative elements do not change positions.", "typeRef": "Page.Viewport"}, {"name": "displayFeature", "type": "object", "optional": true, "description": "If set, the display feature of a multi-segment screen. If not set, multi-segment support is turned-off. Deprecated, use Emulation.setDisplayFeaturesOverride.", "typeRef": "Emulation.DisplayFeature"}, {"name": "devicePosture", "type": "object", "optional": true, "description": "If set, the posture of a foldable device. If not set the posture is set to continuous. Deprecated, use Emulation.setDevicePostureOverride.", "typeRef": "Emulation.DevicePosture"}, {"name": "scrollbarType", "type": "string", "optional": true, "description": "Scrollbar type. Default: `default`.", "typeRef": "Emulation.SetDeviceMetricsOverrideRequestScrollbarType"}, {"name": "screenOrientationLockEmulation", "type": "boolean", "optional": true, "description": "If set to true, enables screen orientation lock emulation, which intercepts screen.orientation.lock() calls from the page and reports orientation changes via screenOrientationLockChanged events. This is useful for emulating mobile device orientation lock behavior in responsive design mode.", "typeRef": null}, {"name": "viewportMeta", "type": "string", "optional": true, "description": "Viewport meta tag behavior. Default: `default`. Note: if `mobile` is `true`, the viewport meta tag is always enabled.", "typeRef": "Emulation.SetDeviceMetricsOverrideRequestViewportMeta"}, {"name": "textLayoutMode", "type": "string", "optional": true, "description": "Text layout mode. Default: `default`. Note: if `mobile` is `true`, mobile text layout mode (text autosizing) is always enabled.", "typeRef": "Emulation.SetDeviceMetricsOverrideRequestTextLayoutMode"}], [], "Overrides the values of device screen dimensions (window.screen.width, window.screen.height, window.innerWidth, window.innerHeight, and \"device-width\"/\"device-height\"-related CSS media query results).");
|
|
549
550
|
inspectorBackend.registerCommand("Emulation.setDevicePostureOverride", [{"name": "posture", "type": "object", "optional": false, "description": "", "typeRef": "Emulation.DevicePosture"}], [], "Start reporting the given posture value to the Device Posture API. This override can also be set in setDeviceMetricsOverride().");
|
|
550
551
|
inspectorBackend.registerCommand("Emulation.clearDevicePostureOverride", [], [], "Clears a device posture override set with either setDeviceMetricsOverride() or setDevicePostureOverride() and starts using posture information from the platform again. Does nothing if no override is set.");
|
|
551
552
|
inspectorBackend.registerCommand("Emulation.setDisplayFeaturesOverride", [{"name": "features", "type": "array", "optional": false, "description": "", "typeRef": "Emulation.DisplayFeature"}], [], "Start using the given display features to pupulate the Viewport Segments API. This override can also be set in setDeviceMetricsOverride().");
|
|
@@ -1240,7 +1241,7 @@ inspectorBackend.registerEvent("Preload.ruleSetUpdated", ["ruleSet"]);
|
|
|
1240
1241
|
inspectorBackend.registerEvent("Preload.ruleSetRemoved", ["id"]);
|
|
1241
1242
|
inspectorBackend.registerEvent("Preload.preloadEnabledStateUpdated", ["disabledByPreference", "disabledByDataSaver", "disabledByBatterySaver", "disabledByHoldbackPrefetchSpeculationRules", "disabledByHoldbackPrerenderSpeculationRules"]);
|
|
1242
1243
|
inspectorBackend.registerEvent("Preload.prefetchStatusUpdated", ["key", "pipelineId", "initiatingFrameId", "prefetchUrl", "status", "prefetchStatus", "requestId"]);
|
|
1243
|
-
inspectorBackend.registerEvent("Preload.prerenderStatusUpdated", ["key", "pipelineId", "status", "prerenderStatus", "disallowedMojoInterface", "mismatchedHeaders"]);
|
|
1244
|
+
inspectorBackend.registerEvent("Preload.prerenderStatusUpdated", ["key", "pipelineId", "status", "effectiveAction", "prerenderStatus", "disallowedMojoInterface", "mismatchedHeaders"]);
|
|
1244
1245
|
inspectorBackend.registerEvent("Preload.preloadingAttemptSourcesUpdated", ["loaderId", "preloadingAttemptSources"]);
|
|
1245
1246
|
inspectorBackend.registerCommand("Preload.enable", [], [], "");
|
|
1246
1247
|
inspectorBackend.registerCommand("Preload.disable", [], [], "");
|
|
@@ -7517,6 +7517,11 @@ export namespace Emulation {
|
|
|
7517
7517
|
Default = 'default',
|
|
7518
7518
|
}
|
|
7519
7519
|
|
|
7520
|
+
export const enum SetDeviceMetricsOverrideRequestTextLayoutMode {
|
|
7521
|
+
Mobile = 'mobile',
|
|
7522
|
+
Default = 'default',
|
|
7523
|
+
}
|
|
7524
|
+
|
|
7520
7525
|
export interface SetDeviceMetricsOverrideRequest {
|
|
7521
7526
|
/**
|
|
7522
7527
|
* Overriding width value in pixels (minimum 0, maximum 10000000). 0 disables the override.
|
|
@@ -7599,6 +7604,11 @@ export namespace Emulation {
|
|
|
7599
7604
|
* the viewport meta tag is always enabled.
|
|
7600
7605
|
*/
|
|
7601
7606
|
viewportMeta?: SetDeviceMetricsOverrideRequestViewportMeta;
|
|
7607
|
+
/**
|
|
7608
|
+
* Text layout mode. Default: `default`. Note: if `mobile` is `true`,
|
|
7609
|
+
* mobile text layout mode (text autosizing) is always enabled.
|
|
7610
|
+
*/
|
|
7611
|
+
textLayoutMode?: SetDeviceMetricsOverrideRequestTextLayoutMode;
|
|
7602
7612
|
}
|
|
7603
7613
|
|
|
7604
7614
|
export interface SetDevicePostureOverrideRequest {
|
|
@@ -17557,6 +17567,12 @@ export namespace Preload {
|
|
|
17557
17567
|
key: PreloadingAttemptKey;
|
|
17558
17568
|
pipelineId: PreloadPipelineId;
|
|
17559
17569
|
status: PreloadingStatus;
|
|
17570
|
+
/**
|
|
17571
|
+
* The action currently performed by this attempt. This differs from
|
|
17572
|
+
* `key.action` after a prerender-until-script attempt is upgraded in place
|
|
17573
|
+
* to a full prerender.
|
|
17574
|
+
*/
|
|
17575
|
+
effectiveAction?: SpeculationAction;
|
|
17560
17576
|
prerenderStatus?: PrerenderFinalStatus;
|
|
17561
17577
|
/**
|
|
17562
17578
|
* This is used to give users more information about the name of Mojo interface
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
import * as Platform from '../../core/platform/platform.js';
|
|
6
6
|
import * as FormatterActions from '../../entrypoints/formatter_actions/formatter_actions.js';
|
|
7
7
|
|
|
8
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
DefinitionKind,
|
|
10
|
+
ScopeKind,
|
|
11
|
+
type ScopeTreeNode,
|
|
12
|
+
type ScopeVariableMapping,
|
|
13
|
+
} from '../../entrypoints/formatter_actions/formatter_actions.js';
|
|
9
14
|
|
|
10
15
|
let formatterWorkerPoolInstance: FormatterWorkerPool|undefined;
|
|
11
16
|
|
|
@@ -134,8 +139,8 @@ export class FormatterWorkerPool {
|
|
|
134
139
|
return this.runTask(FormatterActions.FormatterActions.FORMAT, parameters) as Promise<FormatterActions.FormatResult>;
|
|
135
140
|
}
|
|
136
141
|
|
|
137
|
-
javaScriptSubstitute(expression: string, mapping:
|
|
138
|
-
if (mapping.every(
|
|
142
|
+
javaScriptSubstitute(expression: string, mapping: FormatterActions.ScopeVariableMapping[]): Promise<string> {
|
|
143
|
+
if (mapping.every(scope => scope.bindings.size === 0)) {
|
|
139
144
|
return Promise.resolve(expression);
|
|
140
145
|
}
|
|
141
146
|
return this.runTask(FormatterActions.FormatterActions.JAVASCRIPT_SUBSTITUTE, {content: expression, mapping})
|
|
@@ -9,13 +9,25 @@ import * as Protocol from '../../generated/protocol.js';
|
|
|
9
9
|
import type * as Bindings from '../bindings/bindings.js';
|
|
10
10
|
import * as Formatter from '../formatter/formatter.js';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* The result of resolving a generated scope via the source map's mappings/names.
|
|
14
|
+
*/
|
|
15
|
+
interface ResolvedScope {
|
|
16
|
+
/** Generated name -> authored name, for generated names that we could map. */
|
|
17
|
+
variableMapping: Map<string, string>;
|
|
18
|
+
thisMapping: string|null;
|
|
19
|
+
/** All generated names declared by the scope, including ones that we couldn't map. */
|
|
20
|
+
generatedNames: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
interface CachedScopeMap {
|
|
13
24
|
sourceMap: SDK.SourceMap.SourceMap|undefined;
|
|
14
|
-
mappingPromise: Promise<
|
|
25
|
+
mappingPromise: Promise<ResolvedScope>;
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
const scopeToCachedIdentifiersMap = new WeakMap<Formatter.FormatterWorkerPool.ScopeTreeNode, CachedScopeMap>();
|
|
18
|
-
const cachedMapByCallFrame =
|
|
29
|
+
const cachedMapByCallFrame =
|
|
30
|
+
new WeakMap<SDK.DebuggerModel.CallFrame, Formatter.FormatterWorkerPool.ScopeVariableMapping[]>();
|
|
19
31
|
|
|
20
32
|
export async function getTextFor(contentProvider: TextUtils.ContentProvider.ContentProvider):
|
|
21
33
|
Promise<TextUtils.Text.Text|null> {
|
|
@@ -193,13 +205,13 @@ const enum Punctuation {
|
|
|
193
205
|
const resolveDebuggerScope =
|
|
194
206
|
async(scope: SDK.DebuggerModel.ScopeChainEntry,
|
|
195
207
|
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
|
|
196
|
-
Promise<
|
|
208
|
+
Promise<ResolvedScope> => {
|
|
197
209
|
if (!scope.callFrame()
|
|
198
210
|
.debuggerModel.target()
|
|
199
211
|
.targetManager()
|
|
200
212
|
.settings.resolve(SDK.SDKSettings.jsSourceMapsEnabledSettingDescriptor)
|
|
201
213
|
.get()) {
|
|
202
|
-
return {variableMapping: new Map(), thisMapping: null};
|
|
214
|
+
return {variableMapping: new Map(), thisMapping: null, generatedNames: []};
|
|
203
215
|
}
|
|
204
216
|
const script = scope.callFrame().script;
|
|
205
217
|
const scopeChain = await findScopeChainForDebuggerScope(scope);
|
|
@@ -208,10 +220,10 @@ const resolveDebuggerScope =
|
|
|
208
220
|
|
|
209
221
|
const resolveScope = async(script: SDK.Script.Script, scopeChain: Formatter.FormatterWorkerPool.ScopeTreeNode[],
|
|
210
222
|
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
|
|
211
|
-
Promise<
|
|
223
|
+
Promise<ResolvedScope> => {
|
|
212
224
|
const parsedScope = scopeChain[scopeChain.length - 1];
|
|
213
225
|
if (!parsedScope) {
|
|
214
|
-
return {variableMapping: new Map<string, string>(), thisMapping: null};
|
|
226
|
+
return {variableMapping: new Map<string, string>(), thisMapping: null, generatedNames: []};
|
|
215
227
|
}
|
|
216
228
|
let cachedScopeMap = scopeToCachedIdentifiersMap.get(parsedScope);
|
|
217
229
|
const sourceMap = script.sourceMap();
|
|
@@ -223,7 +235,7 @@ const resolveScope = async(script: SDK.Script.Script, scopeChain: Formatter.Form
|
|
|
223
235
|
let thisMapping = null;
|
|
224
236
|
|
|
225
237
|
if (!sourceMap) {
|
|
226
|
-
return {variableMapping, thisMapping};
|
|
238
|
+
return {variableMapping, thisMapping, generatedNames: []};
|
|
227
239
|
}
|
|
228
240
|
// Extract as much as possible from SourceMap and resolve
|
|
229
241
|
// missing identifier names from SourceMap ranges.
|
|
@@ -261,7 +273,7 @@ const resolveScope = async(script: SDK.Script.Script, scopeChain: Formatter.Form
|
|
|
261
273
|
|
|
262
274
|
const parsedVariables = await scopeIdentifiers(script, parsedScope, scopeChain.slice(0, -1));
|
|
263
275
|
if (!parsedVariables) {
|
|
264
|
-
return {variableMapping, thisMapping};
|
|
276
|
+
return {variableMapping, thisMapping, generatedNames: []};
|
|
265
277
|
}
|
|
266
278
|
for (const id of parsedVariables.boundVariables) {
|
|
267
279
|
resolveEntry(id, sourceName => {
|
|
@@ -279,7 +291,8 @@ const resolveScope = async(script: SDK.Script.Script, scopeChain: Formatter.Form
|
|
|
279
291
|
});
|
|
280
292
|
}
|
|
281
293
|
await Promise.all(promises).then(getScopeResolvedForTest());
|
|
282
|
-
|
|
294
|
+
const generatedNames = parsedVariables.boundVariables.map(id => id.name);
|
|
295
|
+
return {variableMapping, thisMapping, generatedNames};
|
|
283
296
|
})();
|
|
284
297
|
cachedScopeMap = {sourceMap, mappingPromise: identifiersPromise};
|
|
285
298
|
scopeToCachedIdentifiersMap.set(parsedScope, {sourceMap, mappingPromise: identifiersPromise});
|
|
@@ -403,23 +416,39 @@ export const resolveScopeChain =
|
|
|
403
416
|
return scopes.map(scope => new ScopeWithSourceMappedVariables(scope, thisObject, debuggerWorkspaceBinding));
|
|
404
417
|
};
|
|
405
418
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Converts the generated name -> authored name mapping of a resolved scope into the
|
|
421
|
+
* authored name -> generated name form expected by `javaScriptSubstitute`.
|
|
422
|
+
*/
|
|
423
|
+
function toScopeVariableMapping({variableMapping, generatedNames}: ResolvedScope):
|
|
424
|
+
Formatter.FormatterWorkerPool.ScopeVariableMapping {
|
|
425
|
+
const bindings = new Map<string, string|null>();
|
|
426
|
+
for (const [generatedName, authoredName] of variableMapping) {
|
|
427
|
+
// Multiple generated names can map to the same authored name. We pick the first one. The worker still
|
|
428
|
+
// learns about the others via `generatedNames`, which it needs to detect shadowing of outer bindings.
|
|
429
|
+
if (authoredName && !bindings.has(authoredName)) {
|
|
430
|
+
bindings.set(authoredName, generatedName);
|
|
411
431
|
}
|
|
412
432
|
}
|
|
413
|
-
return
|
|
433
|
+
return {bindings, generatedNames};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Source map scopes don't tell us which generated names a generated range declares. The worker falls back
|
|
438
|
+
* to the free identifiers of the binding expressions in that case.
|
|
439
|
+
*/
|
|
440
|
+
function fromSourceMapScopes(mappedVariables: Array<Map<string, string|null>>):
|
|
441
|
+
Formatter.FormatterWorkerPool.ScopeVariableMapping[] {
|
|
442
|
+
return mappedVariables.map(bindings => ({bindings, generatedNames: []}));
|
|
414
443
|
}
|
|
415
444
|
|
|
416
445
|
/**
|
|
417
|
-
* @returns An array of
|
|
446
|
+
* @returns An array of scopes (from inner-most to outer-most) with their original name -> compiled name or binding expression mappings.
|
|
418
447
|
*/
|
|
419
448
|
export const allVariablesInCallFrame = async(
|
|
420
449
|
callFrame: SDK.DebuggerModel.CallFrame,
|
|
421
450
|
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
|
|
422
|
-
Promise<
|
|
451
|
+
Promise<Formatter.FormatterWorkerPool.ScopeVariableMapping[]> => {
|
|
423
452
|
if (!callFrame.debuggerModel.target()
|
|
424
453
|
.targetManager()
|
|
425
454
|
.settings.resolve(SDK.SDKSettings.jsSourceMapsEnabledSettingDescriptor)
|
|
@@ -437,36 +466,37 @@ export const allVariablesInCallFrame = async(
|
|
|
437
466
|
const mappedVariables =
|
|
438
467
|
sourceMap?.resolveMappedVariablesAtPosition(callFrame.location(), callFrame.returnValue() !== null);
|
|
439
468
|
if (mappedVariables) {
|
|
440
|
-
|
|
441
|
-
|
|
469
|
+
const result = fromSourceMapScopes(mappedVariables);
|
|
470
|
+
cachedMapByCallFrame.set(callFrame, result);
|
|
471
|
+
return result;
|
|
442
472
|
}
|
|
443
473
|
}
|
|
444
474
|
|
|
445
475
|
const scopeChain = callFrame.scopeChain().filter(scope => !scope.empty());
|
|
446
|
-
const
|
|
476
|
+
const resolvedScopes =
|
|
447
477
|
await Promise.all(scopeChain.map(scope => resolveDebuggerScope(scope, debuggerWorkspaceBinding)));
|
|
448
|
-
const
|
|
449
|
-
cachedMapByCallFrame.set(callFrame,
|
|
450
|
-
return
|
|
478
|
+
const result = resolvedScopes.map(toScopeVariableMapping);
|
|
479
|
+
cachedMapByCallFrame.set(callFrame, result);
|
|
480
|
+
return result;
|
|
451
481
|
};
|
|
452
482
|
|
|
453
483
|
/**
|
|
454
|
-
* @returns An array of
|
|
484
|
+
* @returns An array of scopes (from inner-most to outer-most) with their original name -> compiled name or binding expression mappings.
|
|
455
485
|
*/
|
|
456
486
|
export const allVariablesAtPosition =
|
|
457
487
|
async(location: SDK.DebuggerModel.Location,
|
|
458
488
|
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
|
|
459
|
-
Promise<
|
|
460
|
-
const
|
|
489
|
+
Promise<Formatter.FormatterWorkerPool.ScopeVariableMapping[]> => {
|
|
490
|
+
const result: Formatter.FormatterWorkerPool.ScopeVariableMapping[] = [];
|
|
461
491
|
const script = location.script();
|
|
462
492
|
if (!script) {
|
|
463
|
-
return
|
|
493
|
+
return result;
|
|
464
494
|
}
|
|
465
495
|
if (!script.debuggerModel.target()
|
|
466
496
|
.targetManager()
|
|
467
497
|
.settings.resolve(SDK.SDKSettings.jsSourceMapsEnabledSettingDescriptor)
|
|
468
498
|
.get()) {
|
|
469
|
-
return
|
|
499
|
+
return result;
|
|
470
500
|
}
|
|
471
501
|
|
|
472
502
|
if (Root.Runtime.hostConfig.devToolsSourceMapScopesInSourcesPanel?.enabled) {
|
|
@@ -474,13 +504,13 @@ export const allVariablesAtPosition =
|
|
|
474
504
|
script.sourceMap() ?? await script.debuggerModel.sourceMapManager().sourceMapForClientPromise(script);
|
|
475
505
|
const mappedVariables = sourceMap?.resolveMappedVariablesAtPosition(location);
|
|
476
506
|
if (mappedVariables) {
|
|
477
|
-
return mappedVariables;
|
|
507
|
+
return fromSourceMapScopes(mappedVariables);
|
|
478
508
|
}
|
|
479
509
|
}
|
|
480
510
|
|
|
481
511
|
const scopeTreeAndText = await computeScopeTree(script);
|
|
482
512
|
if (!scopeTreeAndText) {
|
|
483
|
-
return
|
|
513
|
+
return result;
|
|
484
514
|
}
|
|
485
515
|
|
|
486
516
|
const {scopeTree, text} = scopeTreeAndText;
|
|
@@ -489,11 +519,10 @@ export const allVariablesAtPosition =
|
|
|
489
519
|
const scopeChain = findScopeChain(scopeTree, {start: locationOffset, end: locationOffset});
|
|
490
520
|
|
|
491
521
|
while (scopeChain.length > 0) {
|
|
492
|
-
|
|
493
|
-
reverseMapping.push(reverseScopeMapping(variableMapping));
|
|
522
|
+
result.push(toScopeVariableMapping(await resolveScope(script, scopeChain, debuggerWorkspaceBinding)));
|
|
494
523
|
scopeChain.pop();
|
|
495
524
|
}
|
|
496
|
-
return
|
|
525
|
+
return result;
|
|
497
526
|
};
|
|
498
527
|
|
|
499
528
|
export const resolveThisObject = async(
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import * as Common from '../../core/common/common.js';
|
|
6
|
-
import * as SDK from '../../core/sdk/sdk.js';
|
|
7
|
-
import type * as Bindings from '../bindings/bindings.js';
|
|
6
|
+
import type * as SDK from '../../core/sdk/sdk.js';
|
|
8
7
|
|
|
9
|
-
import {
|
|
8
|
+
import {Events as ScopeChainResolverEvents, type ScopeChainResolver} from './ScopeChainResolver.js';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* This class is responsible for resolving / updating the scope chain for a specific {@link SDK.DebuggerModel.CallFrame}
|
|
@@ -18,57 +17,35 @@ import {resolveScopeChain} from './NamesResolver.js';
|
|
|
18
17
|
*
|
|
19
18
|
* Source maps can be enabled/disabled dynamically and debugger plugins can attach debug info after the fact.
|
|
20
19
|
*
|
|
21
|
-
*
|
|
20
|
+
* The {@link ScopeChainResolver} tracks all that and invalidates its cache accordingly. This class
|
|
21
|
+
* sends events with the latest scope chain for a specific call frame.
|
|
22
22
|
*/
|
|
23
23
|
export class ScopeChainModel extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
24
|
-
static readonly #cachedScopeChainByCallFrame =
|
|
25
|
-
new WeakMap<SDK.DebuggerModel.CallFrame, Promise<SDK.DebuggerModel.ScopeChainEntry[]>>();
|
|
26
|
-
|
|
27
24
|
readonly #callFrame: SDK.DebuggerModel.CallFrame;
|
|
28
|
-
readonly #
|
|
25
|
+
readonly #scopeChainResolver: ScopeChainResolver;
|
|
29
26
|
|
|
30
27
|
/** We use the `Throttler` here to make sure that `#boundUpdate` is not run multiple times simultanously */
|
|
31
28
|
readonly #throttler = new Common.Throttler.Throttler(5);
|
|
32
29
|
readonly #boundUpdate = this.#update.bind(this);
|
|
33
30
|
|
|
34
|
-
constructor(callFrame: SDK.DebuggerModel.CallFrame,
|
|
35
|
-
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding) {
|
|
31
|
+
constructor(callFrame: SDK.DebuggerModel.CallFrame, scopeChainResolver: ScopeChainResolver) {
|
|
36
32
|
super();
|
|
37
33
|
this.#callFrame = callFrame;
|
|
38
|
-
this.#
|
|
39
|
-
this.#
|
|
40
|
-
|
|
41
|
-
this.#callFrame.debuggerModel.sourceMapManager().addEventListener(SDK.SourceMapManager.Events.SourceMapAttached,
|
|
42
|
-
this.#sourceMapChanged, this);
|
|
43
|
-
this.#callFrame.debuggerModel.sourceMapManager().addEventListener(SDK.SourceMapManager.Events.SourceMapDetached,
|
|
44
|
-
this.#sourceMapChanged, this);
|
|
34
|
+
this.#scopeChainResolver = scopeChainResolver;
|
|
35
|
+
this.#scopeChainResolver.addEventListener(ScopeChainResolverEvents.SCOPE_CHAIN_INVALIDATED,
|
|
36
|
+
this.#scopeChainInvalidated, this);
|
|
45
37
|
|
|
46
38
|
void this.#throttler.schedule(this.#boundUpdate);
|
|
47
39
|
}
|
|
48
40
|
|
|
49
41
|
dispose(): void {
|
|
50
|
-
this.#
|
|
51
|
-
|
|
52
|
-
this.#callFrame.debuggerModel.sourceMapManager().removeEventListener(SDK.SourceMapManager.Events.SourceMapAttached,
|
|
53
|
-
this.#sourceMapChanged, this);
|
|
54
|
-
this.#callFrame.debuggerModel.sourceMapManager().removeEventListener(SDK.SourceMapManager.Events.SourceMapDetached,
|
|
55
|
-
this.#sourceMapChanged, this);
|
|
42
|
+
this.#scopeChainResolver.removeEventListener(ScopeChainResolverEvents.SCOPE_CHAIN_INVALIDATED,
|
|
43
|
+
this.#scopeChainInvalidated, this);
|
|
56
44
|
this.listeners?.clear();
|
|
57
45
|
}
|
|
58
46
|
|
|
59
|
-
static resolveScopeChain(callFrame: SDK.DebuggerModel.CallFrame,
|
|
60
|
-
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
|
|
61
|
-
Promise<SDK.DebuggerModel.ScopeChainEntry[]> {
|
|
62
|
-
let cachedPromise = ScopeChainModel.#cachedScopeChainByCallFrame.get(callFrame);
|
|
63
|
-
if (!cachedPromise) {
|
|
64
|
-
cachedPromise = resolveScopeChain(callFrame, debuggerWorkspaceBinding);
|
|
65
|
-
ScopeChainModel.#cachedScopeChainByCallFrame.set(callFrame, cachedPromise);
|
|
66
|
-
}
|
|
67
|
-
return cachedPromise;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
47
|
resolveScopeChain(): Promise<SDK.DebuggerModel.ScopeChainEntry[]> {
|
|
71
|
-
return
|
|
48
|
+
return this.#scopeChainResolver.resolveScopeChain(this.#callFrame);
|
|
72
49
|
}
|
|
73
50
|
|
|
74
51
|
async #update(): Promise<void> {
|
|
@@ -76,17 +53,8 @@ export class ScopeChainModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
76
53
|
this.dispatchEventToListeners(Events.SCOPE_CHAIN_UPDATED, new ScopeChain(scopeChain));
|
|
77
54
|
}
|
|
78
55
|
|
|
79
|
-
#
|
|
56
|
+
#scopeChainInvalidated(event: Common.EventTarget.EventTargetEvent<SDK.Script.Script>): void {
|
|
80
57
|
if (event.data === this.#callFrame.script) {
|
|
81
|
-
ScopeChainModel.#cachedScopeChainByCallFrame.delete(this.#callFrame);
|
|
82
|
-
void this.#throttler.schedule(this.#boundUpdate);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
#sourceMapChanged(event: Common.EventTarget
|
|
87
|
-
.EventTargetEvent<{client: SDK.Script.Script, sourceMap: SDK.SourceMap.SourceMap}>): void {
|
|
88
|
-
if (event.data.client === this.#callFrame.script) {
|
|
89
|
-
ScopeChainModel.#cachedScopeChainByCallFrame.delete(this.#callFrame);
|
|
90
58
|
void this.#throttler.schedule(this.#boundUpdate);
|
|
91
59
|
}
|
|
92
60
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
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 Common from '../../core/common/common.js';
|
|
6
|
+
import * as Root from '../../core/root/root.js';
|
|
7
|
+
import * as SDK from '../../core/sdk/sdk.js';
|
|
8
|
+
import type * as Bindings from '../bindings/bindings.js';
|
|
9
|
+
|
|
10
|
+
import {resolveScopeChain} from './NamesResolver.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves and caches the scope chain for {@link SDK.DebuggerModel.CallFrame}s.
|
|
14
|
+
*
|
|
15
|
+
* There is one instance per `Universe`. Caching the resolved scope chain allows different consumers
|
|
16
|
+
* (e.g. the Scope view and the inline values in the editor) to share the exact same
|
|
17
|
+
* {@link SDK.DebuggerModel.ScopeChainEntry} and {@link SDK.RemoteObject.RemoteObject} instances for a
|
|
18
|
+
* given pause, without redundant CDP evaluations.
|
|
19
|
+
*
|
|
20
|
+
* The cache for a script is invalidated whenever debugging info (e.g. from a debugger language plugin)
|
|
21
|
+
* or a source map is attached to or detached from that script. The {@link Events.SCOPE_CHAIN_INVALIDATED}
|
|
22
|
+
* event is dispatched afterwards, so consumers can re-resolve the scope chain.
|
|
23
|
+
*/
|
|
24
|
+
export class ScopeChainResolver extends Common.ObjectWrapper.ObjectWrapper<EventTypes> implements
|
|
25
|
+
SDK.TargetManager.SDKModelObserver<SDK.DebuggerModel.DebuggerModel> {
|
|
26
|
+
readonly #debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding;
|
|
27
|
+
readonly #cache = new WeakMap<SDK.Script.Script,
|
|
28
|
+
WeakMap<SDK.DebuggerModel.CallFrame, Promise<SDK.DebuggerModel.ScopeChainEntry[]>>>();
|
|
29
|
+
|
|
30
|
+
constructor(targetManager: SDK.TargetManager.TargetManager,
|
|
31
|
+
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding) {
|
|
32
|
+
super();
|
|
33
|
+
this.#debuggerWorkspaceBinding = debuggerWorkspaceBinding;
|
|
34
|
+
targetManager.observeModels(SDK.DebuggerModel.DebuggerModel, this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @deprecated Pass the `ScopeChainResolver` of the `Universe` via constructor instead.
|
|
39
|
+
*/
|
|
40
|
+
static instance(): ScopeChainResolver {
|
|
41
|
+
// TODO(crbug.com/458180550): Remove once all callers receive the instance via constructor.
|
|
42
|
+
return Root.DevToolsContext.globalInstance().get(ScopeChainResolver);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
modelAdded(debuggerModel: SDK.DebuggerModel.DebuggerModel): void {
|
|
46
|
+
debuggerModel.addEventListener(SDK.DebuggerModel.Events.DebugInfoAttached, this.#debugInfoAttached, this);
|
|
47
|
+
debuggerModel.sourceMapManager().addEventListener(SDK.SourceMapManager.Events.SourceMapAttached,
|
|
48
|
+
this.#sourceMapChanged, this);
|
|
49
|
+
debuggerModel.sourceMapManager().addEventListener(SDK.SourceMapManager.Events.SourceMapDetached,
|
|
50
|
+
this.#sourceMapChanged, this);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
modelRemoved(debuggerModel: SDK.DebuggerModel.DebuggerModel): void {
|
|
54
|
+
debuggerModel.removeEventListener(SDK.DebuggerModel.Events.DebugInfoAttached, this.#debugInfoAttached, this);
|
|
55
|
+
debuggerModel.sourceMapManager().removeEventListener(SDK.SourceMapManager.Events.SourceMapAttached,
|
|
56
|
+
this.#sourceMapChanged, this);
|
|
57
|
+
debuggerModel.sourceMapManager().removeEventListener(SDK.SourceMapManager.Events.SourceMapDetached,
|
|
58
|
+
this.#sourceMapChanged, this);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns the (cached) resolved scope chain for `callFrame`. Repeated calls return the same promise
|
|
63
|
+
* until the cache for the call frame's script is invalidated.
|
|
64
|
+
*/
|
|
65
|
+
resolveScopeChain(callFrame: SDK.DebuggerModel.CallFrame): Promise<SDK.DebuggerModel.ScopeChainEntry[]> {
|
|
66
|
+
let cacheForScript = this.#cache.get(callFrame.script);
|
|
67
|
+
if (!cacheForScript) {
|
|
68
|
+
cacheForScript = new WeakMap();
|
|
69
|
+
this.#cache.set(callFrame.script, cacheForScript);
|
|
70
|
+
}
|
|
71
|
+
let cachedPromise = cacheForScript.get(callFrame);
|
|
72
|
+
if (!cachedPromise) {
|
|
73
|
+
cachedPromise = resolveScopeChain(callFrame, this.#debuggerWorkspaceBinding);
|
|
74
|
+
cacheForScript.set(callFrame, cachedPromise);
|
|
75
|
+
}
|
|
76
|
+
return cachedPromise;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#invalidate(script: SDK.Script.Script): void {
|
|
80
|
+
this.#cache.delete(script);
|
|
81
|
+
this.dispatchEventToListeners(Events.SCOPE_CHAIN_INVALIDATED, script);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#debugInfoAttached(event: Common.EventTarget.EventTargetEvent<SDK.Script.Script>): void {
|
|
85
|
+
this.#invalidate(event.data);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#sourceMapChanged(event: Common.EventTarget
|
|
89
|
+
.EventTargetEvent<{client: SDK.Script.Script, sourceMap: SDK.SourceMap.SourceMap}>): void {
|
|
90
|
+
this.#invalidate(event.data.client);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const enum Events {
|
|
95
|
+
SCOPE_CHAIN_INVALIDATED = 'ScopeChainInvalidated',
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface EventTypes {
|
|
99
|
+
/** Dispatched with the script for which the cached scope chains have been invalidated. */
|
|
100
|
+
[Events.SCOPE_CHAIN_INVALIDATED]: SDK.Script.Script;
|
|
101
|
+
}
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
import * as FunctionCodeResolver from './FunctionCodeResolver.js';
|
|
6
6
|
import * as NamesResolver from './NamesResolver.js';
|
|
7
7
|
import * as ScopeChainModel from './ScopeChainModel.js';
|
|
8
|
+
import * as ScopeChainResolver from './ScopeChainResolver.js';
|
|
8
9
|
|
|
9
10
|
export {
|
|
10
11
|
FunctionCodeResolver,
|
|
11
12
|
NamesResolver,
|
|
12
13
|
ScopeChainModel,
|
|
14
|
+
ScopeChainResolver,
|
|
13
15
|
};
|
|
@@ -459,7 +459,8 @@ export class ConsolePrompt extends ConsolePromptBase {
|
|
|
459
459
|
?.evaluateCommandInConsole(executionContext, message, expression, useCommandLineAPI);
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
-
private async substituteNames(expression: string,
|
|
462
|
+
private async substituteNames(expression: string,
|
|
463
|
+
mapping: Formatter.FormatterWorkerPool.ScopeVariableMapping[]): Promise<string> {
|
|
463
464
|
try {
|
|
464
465
|
return await Formatter.FormatterWorkerPool.formatterWorkerPool().javaScriptSubstitute(expression, mapping);
|
|
465
466
|
} catch {
|
|
@@ -1002,8 +1002,8 @@ export class DebuggerPlugin extends Plugin {
|
|
|
1002
1002
|
|
|
1003
1003
|
const uiPositionToEditorOffset = (lineNumber: number, columnNumber: number): number|null =>
|
|
1004
1004
|
this.editor?.toOffset(this.transformer.uiLocationToEditorLocation(lineNumber, columnNumber)) ?? null;
|
|
1005
|
-
const scopeChain =
|
|
1006
|
-
|
|
1005
|
+
const scopeChain =
|
|
1006
|
+
await SourceMapScopes.ScopeChainResolver.ScopeChainResolver.instance().resolveScopeChain(callFrame);
|
|
1007
1007
|
const localOriginalScope = scopeChain
|
|
1008
1008
|
.find((s): s is SDK.SourceMapScopeChainEntry.SourceMapScopeChainEntry =>
|
|
1009
1009
|
s instanceof SDK.SourceMapScopeChainEntry.SourceMapScopeChainEntry &&
|
|
@@ -2184,8 +2184,7 @@ export async function computeScopeMappings(
|
|
|
2184
2184
|
resolvedScopeChain?: SDK.DebuggerModel.ScopeChainEntry[]): Promise<ScopeMapping[]> {
|
|
2185
2185
|
const scopeMappings: ScopeMapping[] = [];
|
|
2186
2186
|
const scopeChain = resolvedScopeChain ??
|
|
2187
|
-
await SourceMapScopes.
|
|
2188
|
-
callFrame, Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance());
|
|
2187
|
+
await SourceMapScopes.ScopeChainResolver.ScopeChainResolver.instance().resolveScopeChain(callFrame);
|
|
2189
2188
|
const activeScopes =
|
|
2190
2189
|
new Set(scopeChain.filter(s => s instanceof SDK.SourceMapScopeChainEntry.SourceMapScopeChainEntry)
|
|
2191
2190
|
.map(s => s.originalScope()));
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
import * as i18n from '../../core/i18n/i18n.js';
|
|
32
32
|
import type * as SDK from '../../core/sdk/sdk.js';
|
|
33
33
|
import * as Protocol from '../../generated/protocol.js';
|
|
34
|
-
import * as Bindings from '../../models/bindings/bindings.js';
|
|
35
34
|
import * as SourceMapScopes from '../../models/source_map_scopes/source_map_scopes.js';
|
|
36
35
|
import * as StackTrace from '../../models/stack_trace/stack_trace.js';
|
|
37
36
|
import * as ObjectUI from '../../ui/legacy/components/object_ui/object_ui.js';
|
|
@@ -214,8 +213,9 @@ export class ScopeChainSidebarPane extends UI.Widget.VBox implements UI.ContextF
|
|
|
214
213
|
this.#linkifier.reset();
|
|
215
214
|
|
|
216
215
|
if (callFrame) {
|
|
216
|
+
// TODO(crbug.com/458180550): Receive the ScopeChainResolver via constructor.
|
|
217
217
|
const scopeChainModel = new SourceMapScopes.ScopeChainModel.ScopeChainModel(
|
|
218
|
-
callFrame.sdkFrame,
|
|
218
|
+
callFrame.sdkFrame, SourceMapScopes.ScopeChainResolver.ScopeChainResolver.instance());
|
|
219
219
|
this.#scopeChainModel = scopeChainModel;
|
|
220
220
|
this.#scopeChainModel.addEventListener(SourceMapScopes.ScopeChainModel.Events.SCOPE_CHAIN_UPDATED, event => {
|
|
221
221
|
if (this.#scopeChainModel === scopeChainModel) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Name: Dependencies sourced from the upstream `chromium` repository
|
|
2
2
|
URL: Internal
|
|
3
3
|
Version: N/A
|
|
4
|
-
Revision:
|
|
4
|
+
Revision: cd8ab87ab38bee0c301e4ba26302fd1c7e38377a
|
|
5
5
|
Update Mechanism: Manual (https://crbug.com/428069060)
|
|
6
6
|
License: BSD-3-Clause
|
|
7
7
|
License File: LICENSE
|
|
@@ -73,11 +73,11 @@ UI.ActionRegistration.registerActionExtension({
|
|
|
73
73
|
bindings: [
|
|
74
74
|
{
|
|
75
75
|
shortcut: 'Ctrl+E',
|
|
76
|
-
platform: UI.ActionRegistration.Platforms.
|
|
76
|
+
platform: UI.ActionRegistration.Platforms.WINDOWS_LINUX,
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
shortcut: 'Meta+E',
|
|
80
|
-
platform: UI.ActionRegistration.Platforms.
|
|
80
|
+
platform: UI.ActionRegistration.Platforms.MAC,
|
|
81
81
|
},
|
|
82
82
|
],
|
|
83
83
|
});
|
|
@@ -87,7 +87,7 @@ UI.ActionRegistration.registerActionExtension({
|
|
|
87
87
|
|
|
88
88
|
#### Context types
|
|
89
89
|
The `contextTypes` property determines the context under which the action is available.
|
|
90
|
-
A context is defined as set 'flavors' or classes, which usually represent views, that are added and removed to the context as the user interacts with the application (e.g when the user moves across views).
|
|
90
|
+
A context is defined as set of 'flavors' or classes, which usually represent views, that are added and removed to the context as the user interacts with the application (e.g when the user moves across views).
|
|
91
91
|
(See [`UI.Context`](./Context.js)).
|
|
92
92
|
Therefore, an action can only be triggered when one of its respective context types is visible.
|
|
93
93
|
These flavors can be retrieved invoking the method `flavors` inside the class `UI.Context.Context`.
|
|
@@ -97,10 +97,10 @@ In this case, the action will always be available, regardless of which context i
|
|
|
97
97
|
|
|
98
98
|
#### Key bindings
|
|
99
99
|
|
|
100
|
-
The `bindings` property defines
|
|
100
|
+
The `bindings` property defines `platform` and `keybindSets` properties along with the `shortcut` property containing the actual keyboard shortcut.
|
|
101
101
|
These two extra properties add more conditions (additional to the ones introduced by the `contextTypes` property discussed before) that need to be fulfilled to trigger a particular action.
|
|
102
102
|
|
|
103
|
-
- `platform` refers to the OS on which DevTools is running and can be either `
|
|
103
|
+
- `platform` refers to the OS on which DevTools is running and can be either `MAC`, `WINDOWS_LINUX` or `WINDOWS`. (See [`UI.ActionRegistration`](./ActionRegistration.ts)).
|
|
104
104
|
- `keybindSets` refers to the selected shortcut preset, which can be changed in `settings -> shortcuts -> “Match shortcuts from preset”`.
|
|
105
105
|
A key binding will be used when the selected preset matches any of the keybind sets declared by the binding.
|
|
106
106
|
|
|
@@ -125,16 +125,16 @@ UI.ActionRegistration.registerActionExtension({
|
|
|
125
125
|
contextTypes: undefined,
|
|
126
126
|
bindings: [
|
|
127
127
|
{
|
|
128
|
-
platform:
|
|
128
|
+
platform: UI.ActionRegistration.Platforms.WINDOWS_LINUX,
|
|
129
129
|
shortcut: 'Esc',
|
|
130
130
|
},
|
|
131
131
|
],
|
|
132
132
|
});
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
These two key bindings both allocate the same key for the `
|
|
135
|
+
These two key bindings both allocate the same key for the `WINDOWS_LINUX` platform (the first one is global across platforms) in the `ElementsPanel` context (the second one is global across context types) and both are global across keybind sets.
|
|
136
136
|
|
|
137
|
-
If key bindings were declared such that **at least one** of the three variables (`platform
|
|
137
|
+
If key bindings were declared such that **at least one** of the three variables (`platform`, `contextTypes` and `keybindSets`) doesn't overlap, then no duplication would occur.
|
|
138
138
|
For example, the following is **not** a form of duplication:
|
|
139
139
|
|
|
140
140
|
```ts
|
|
@@ -147,7 +147,7 @@ UI.ActionRegistration.registerActionExtension({
|
|
|
147
147
|
bindings: [
|
|
148
148
|
{
|
|
149
149
|
shortcut: 'Esc',
|
|
150
|
-
keybindSets: [
|
|
150
|
+
keybindSets: [UI.ActionRegistration.KeybindSet.DEVTOOLS_DEFAULT],
|
|
151
151
|
},
|
|
152
152
|
],
|
|
153
153
|
});
|
|
@@ -160,12 +160,12 @@ UI.ActionRegistration.registerActionExtension({
|
|
|
160
160
|
contextTypes: undefined,
|
|
161
161
|
bindings: [
|
|
162
162
|
{
|
|
163
|
-
platform:
|
|
163
|
+
platform: UI.ActionRegistration.Platforms.WINDOWS_LINUX,
|
|
164
164
|
shortcut: 'Esc',
|
|
165
|
-
keybindSets: [
|
|
165
|
+
keybindSets: [UI.ActionRegistration.KeybindSet.VS_CODE],
|
|
166
166
|
},
|
|
167
167
|
],
|
|
168
168
|
});
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
-
In this case,
|
|
171
|
+
In this case, the keybindings are declared for different keybind sets and thus do not result in duplication.
|
package/package.json
CHANGED