chrome-devtools-frontend 1.0.1703574 → 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 +2 -0
- package/docs/architecture_of_devtools.md +1 -1
- package/docs/cookbook/localization.md +2 -2
- package/front_end/core/common/ParsedURL.ts +54 -0
- package/front_end/core/common/README.md +4 -3
- package/front_end/core/platform/StringUtilities.ts +65 -0
- package/front_end/core/sdk/CPUThrottlingManager.ts +3 -3
- package/front_end/core/sdk/SourceMap.ts +3 -3
- package/front_end/core/sdk/SourceMapScopeChainEntry.ts +4 -3
- package/front_end/core/sdk/SourceMapScopesInfo.ts +17 -5
- 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/entrypoints/main/MainImpl.ts +3 -3
- 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/bindings/CompilerScriptMapping.ts +1 -1
- package/front_end/models/formatter/FormatterWorkerPool.ts +8 -3
- package/front_end/models/har/HARFormat.ts +7 -2
- package/front_end/models/source_map_scopes/NamesResolver.ts +75 -38
- 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/accessibility/AccessibilityAnnouncementRecordingListView.ts +33 -1
- package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +4 -12
- package/front_end/panels/application/ApplicationPanelSidebar.ts +58 -60
- package/front_end/panels/application/BackForwardCacheTreeElement.ts +1 -1
- package/front_end/panels/application/FrameDetailsView.ts +64 -82
- package/front_end/panels/application/OpenedWindowDetailsView.ts +20 -21
- package/front_end/panels/application/PreloadingTreeElement.ts +3 -3
- package/front_end/panels/common/CommentsOverlayWidget.ts +145 -72
- package/front_end/panels/common/commentsOverlay.css +34 -23
- package/front_end/panels/console/ConsolePrompt.ts +2 -1
- package/front_end/panels/elements/DOMTreeWidget.ts +29 -10
- package/front_end/panels/network/RequestPayloadView.ts +7 -2
- package/front_end/panels/network/ShowMoreDetailsWidget.ts +12 -9
- package/front_end/panels/sources/DebuggerPlugin.ts +52 -20
- package/front_end/panels/sources/ScopeChainSidebarPane.ts +2 -2
- package/front_end/panels/sources/SourcesPanel.ts +1 -1
- package/front_end/panels/sources/SourcesView.ts +3 -12
- package/front_end/panels/sources/TabbedEditorContainer.ts +0 -3
- package/front_end/panels/sources/components/HeadersView.ts +193 -123
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +5 -0
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +24 -32
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js +15 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js +8 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts +0 -8
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js +0 -24
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts +12 -0
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js +79 -17
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/types.d.ts +5 -0
- package/front_end/third_party/puppeteer/package/package.json +4 -4
- package/front_end/third_party/puppeteer/package/src/api/ElementHandle.ts +15 -2
- package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +10 -1
- package/front_end/third_party/puppeteer/package/src/common/Debug.ts +0 -27
- package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +96 -17
- package/front_end/third_party/puppeteer/package/src/node-env-setup.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/comments/CommentAnchorResolver.ts +60 -22
- package/front_end/ui/comments/CommentOverlayManager.ts +78 -25
- package/front_end/ui/legacy/README.md +12 -12
- package/front_end/ui/legacy/components/data_grid/DataGridExporter.ts +7 -13
- package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +51 -57
- package/front_end/ui/legacy/components/source_frame/ImageView.ts +101 -42
- package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -2
- package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
- package/package.json +1 -1
|
@@ -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> {
|
|
@@ -119,9 +131,11 @@ export async function findScopeChainForDebuggerScope(scope: SDK.DebuggerModel.Sc
|
|
|
119
131
|
const {scopeTree, text} = scopeTreeAndText;
|
|
120
132
|
|
|
121
133
|
// Compute the offset within the scope tree coordinate space.
|
|
134
|
+
const start = script.rawLocationToRelativeLocation(startLocation);
|
|
135
|
+
const end = script.rawLocationToRelativeLocation(endLocation);
|
|
122
136
|
const scopeOffsets = {
|
|
123
|
-
start: text.offsetFromPosition(
|
|
124
|
-
end: text.offsetFromPosition(
|
|
137
|
+
start: text.offsetFromPosition(start.lineNumber, start.columnNumber),
|
|
138
|
+
end: text.offsetFromPosition(end.lineNumber, end.columnNumber),
|
|
125
139
|
};
|
|
126
140
|
|
|
127
141
|
return findScopeChain(scopeTree, scopeOffsets);
|
|
@@ -191,13 +205,13 @@ const enum Punctuation {
|
|
|
191
205
|
const resolveDebuggerScope =
|
|
192
206
|
async(scope: SDK.DebuggerModel.ScopeChainEntry,
|
|
193
207
|
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
|
|
194
|
-
Promise<
|
|
208
|
+
Promise<ResolvedScope> => {
|
|
195
209
|
if (!scope.callFrame()
|
|
196
210
|
.debuggerModel.target()
|
|
197
211
|
.targetManager()
|
|
198
212
|
.settings.resolve(SDK.SDKSettings.jsSourceMapsEnabledSettingDescriptor)
|
|
199
213
|
.get()) {
|
|
200
|
-
return {variableMapping: new Map(), thisMapping: null};
|
|
214
|
+
return {variableMapping: new Map(), thisMapping: null, generatedNames: []};
|
|
201
215
|
}
|
|
202
216
|
const script = scope.callFrame().script;
|
|
203
217
|
const scopeChain = await findScopeChainForDebuggerScope(scope);
|
|
@@ -206,10 +220,10 @@ const resolveDebuggerScope =
|
|
|
206
220
|
|
|
207
221
|
const resolveScope = async(script: SDK.Script.Script, scopeChain: Formatter.FormatterWorkerPool.ScopeTreeNode[],
|
|
208
222
|
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
|
|
209
|
-
Promise<
|
|
223
|
+
Promise<ResolvedScope> => {
|
|
210
224
|
const parsedScope = scopeChain[scopeChain.length - 1];
|
|
211
225
|
if (!parsedScope) {
|
|
212
|
-
return {variableMapping: new Map<string, string>(), thisMapping: null};
|
|
226
|
+
return {variableMapping: new Map<string, string>(), thisMapping: null, generatedNames: []};
|
|
213
227
|
}
|
|
214
228
|
let cachedScopeMap = scopeToCachedIdentifiersMap.get(parsedScope);
|
|
215
229
|
const sourceMap = script.sourceMap();
|
|
@@ -221,7 +235,7 @@ const resolveScope = async(script: SDK.Script.Script, scopeChain: Formatter.Form
|
|
|
221
235
|
let thisMapping = null;
|
|
222
236
|
|
|
223
237
|
if (!sourceMap) {
|
|
224
|
-
return {variableMapping, thisMapping};
|
|
238
|
+
return {variableMapping, thisMapping, generatedNames: []};
|
|
225
239
|
}
|
|
226
240
|
// Extract as much as possible from SourceMap and resolve
|
|
227
241
|
// missing identifier names from SourceMap ranges.
|
|
@@ -259,7 +273,7 @@ const resolveScope = async(script: SDK.Script.Script, scopeChain: Formatter.Form
|
|
|
259
273
|
|
|
260
274
|
const parsedVariables = await scopeIdentifiers(script, parsedScope, scopeChain.slice(0, -1));
|
|
261
275
|
if (!parsedVariables) {
|
|
262
|
-
return {variableMapping, thisMapping};
|
|
276
|
+
return {variableMapping, thisMapping, generatedNames: []};
|
|
263
277
|
}
|
|
264
278
|
for (const id of parsedVariables.boundVariables) {
|
|
265
279
|
resolveEntry(id, sourceName => {
|
|
@@ -277,7 +291,8 @@ const resolveScope = async(script: SDK.Script.Script, scopeChain: Formatter.Form
|
|
|
277
291
|
});
|
|
278
292
|
}
|
|
279
293
|
await Promise.all(promises).then(getScopeResolvedForTest());
|
|
280
|
-
|
|
294
|
+
const generatedNames = parsedVariables.boundVariables.map(id => id.name);
|
|
295
|
+
return {variableMapping, thisMapping, generatedNames};
|
|
281
296
|
})();
|
|
282
297
|
cachedScopeMap = {sourceMap, mappingPromise: identifiersPromise};
|
|
283
298
|
scopeToCachedIdentifiersMap.set(parsedScope, {sourceMap, mappingPromise: identifiersPromise});
|
|
@@ -401,23 +416,39 @@ export const resolveScopeChain =
|
|
|
401
416
|
return scopes.map(scope => new ScopeWithSourceMappedVariables(scope, thisObject, debuggerWorkspaceBinding));
|
|
402
417
|
};
|
|
403
418
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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);
|
|
409
431
|
}
|
|
410
432
|
}
|
|
411
|
-
return
|
|
433
|
+
return {bindings, generatedNames};
|
|
412
434
|
}
|
|
413
435
|
|
|
414
436
|
/**
|
|
415
|
-
*
|
|
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: []}));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* @returns An array of scopes (from inner-most to outer-most) with their original name -> compiled name or binding expression mappings.
|
|
416
447
|
*/
|
|
417
448
|
export const allVariablesInCallFrame = async(
|
|
418
449
|
callFrame: SDK.DebuggerModel.CallFrame,
|
|
419
450
|
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
|
|
420
|
-
Promise<
|
|
451
|
+
Promise<Formatter.FormatterWorkerPool.ScopeVariableMapping[]> => {
|
|
421
452
|
if (!callFrame.debuggerModel.target()
|
|
422
453
|
.targetManager()
|
|
423
454
|
.settings.resolve(SDK.SDKSettings.jsSourceMapsEnabledSettingDescriptor)
|
|
@@ -435,36 +466,37 @@ export const allVariablesInCallFrame = async(
|
|
|
435
466
|
const mappedVariables =
|
|
436
467
|
sourceMap?.resolveMappedVariablesAtPosition(callFrame.location(), callFrame.returnValue() !== null);
|
|
437
468
|
if (mappedVariables) {
|
|
438
|
-
|
|
439
|
-
|
|
469
|
+
const result = fromSourceMapScopes(mappedVariables);
|
|
470
|
+
cachedMapByCallFrame.set(callFrame, result);
|
|
471
|
+
return result;
|
|
440
472
|
}
|
|
441
473
|
}
|
|
442
474
|
|
|
443
475
|
const scopeChain = callFrame.scopeChain().filter(scope => !scope.empty());
|
|
444
|
-
const
|
|
476
|
+
const resolvedScopes =
|
|
445
477
|
await Promise.all(scopeChain.map(scope => resolveDebuggerScope(scope, debuggerWorkspaceBinding)));
|
|
446
|
-
const
|
|
447
|
-
cachedMapByCallFrame.set(callFrame,
|
|
448
|
-
return
|
|
478
|
+
const result = resolvedScopes.map(toScopeVariableMapping);
|
|
479
|
+
cachedMapByCallFrame.set(callFrame, result);
|
|
480
|
+
return result;
|
|
449
481
|
};
|
|
450
482
|
|
|
451
483
|
/**
|
|
452
|
-
* @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.
|
|
453
485
|
*/
|
|
454
486
|
export const allVariablesAtPosition =
|
|
455
487
|
async(location: SDK.DebuggerModel.Location,
|
|
456
488
|
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
|
|
457
|
-
Promise<
|
|
458
|
-
const
|
|
489
|
+
Promise<Formatter.FormatterWorkerPool.ScopeVariableMapping[]> => {
|
|
490
|
+
const result: Formatter.FormatterWorkerPool.ScopeVariableMapping[] = [];
|
|
459
491
|
const script = location.script();
|
|
460
492
|
if (!script) {
|
|
461
|
-
return
|
|
493
|
+
return result;
|
|
462
494
|
}
|
|
463
495
|
if (!script.debuggerModel.target()
|
|
464
496
|
.targetManager()
|
|
465
497
|
.settings.resolve(SDK.SDKSettings.jsSourceMapsEnabledSettingDescriptor)
|
|
466
498
|
.get()) {
|
|
467
|
-
return
|
|
499
|
+
return result;
|
|
468
500
|
}
|
|
469
501
|
|
|
470
502
|
if (Root.Runtime.hostConfig.devToolsSourceMapScopesInSourcesPanel?.enabled) {
|
|
@@ -472,25 +504,25 @@ export const allVariablesAtPosition =
|
|
|
472
504
|
script.sourceMap() ?? await script.debuggerModel.sourceMapManager().sourceMapForClientPromise(script);
|
|
473
505
|
const mappedVariables = sourceMap?.resolveMappedVariablesAtPosition(location);
|
|
474
506
|
if (mappedVariables) {
|
|
475
|
-
return mappedVariables;
|
|
507
|
+
return fromSourceMapScopes(mappedVariables);
|
|
476
508
|
}
|
|
477
509
|
}
|
|
478
510
|
|
|
479
511
|
const scopeTreeAndText = await computeScopeTree(script);
|
|
480
512
|
if (!scopeTreeAndText) {
|
|
481
|
-
return
|
|
513
|
+
return result;
|
|
482
514
|
}
|
|
483
515
|
|
|
484
516
|
const {scopeTree, text} = scopeTreeAndText;
|
|
485
|
-
const
|
|
517
|
+
const {lineNumber, columnNumber} = script.rawLocationToRelativeLocation(location);
|
|
518
|
+
const locationOffset = text.offsetFromPosition(lineNumber, columnNumber);
|
|
486
519
|
const scopeChain = findScopeChain(scopeTree, {start: locationOffset, end: locationOffset});
|
|
487
520
|
|
|
488
521
|
while (scopeChain.length > 0) {
|
|
489
|
-
|
|
490
|
-
reverseMapping.push(reverseScopeMapping(variableMapping));
|
|
522
|
+
result.push(toScopeVariableMapping(await resolveScope(script, scopeChain, debuggerWorkspaceBinding)));
|
|
491
523
|
scopeChain.pop();
|
|
492
524
|
}
|
|
493
|
-
return
|
|
525
|
+
return result;
|
|
494
526
|
};
|
|
495
527
|
|
|
496
528
|
export const resolveThisObject = async(
|
|
@@ -746,8 +778,8 @@ export class RemoteObject extends SDK.RemoteObject.RemoteObject {
|
|
|
746
778
|
* start or if the function scope does not start with a left paren (e.g., arrow
|
|
747
779
|
* function with one parameter), the resolution returns null.
|
|
748
780
|
**/
|
|
749
|
-
async function getFunctionNameFromScopeStart(
|
|
750
|
-
|
|
781
|
+
async function getFunctionNameFromScopeStart(script: SDK.Script.Script, rawLineNumber: number,
|
|
782
|
+
rawColumnNumber: number): Promise<string|null> {
|
|
751
783
|
// To reduce the overhead of resolving function names,
|
|
752
784
|
// we check for source maps first and immediately leave
|
|
753
785
|
// this function if the script doesn't have a sourcemap.
|
|
@@ -756,6 +788,11 @@ async function getFunctionNameFromScopeStart(
|
|
|
756
788
|
return null;
|
|
757
789
|
}
|
|
758
790
|
|
|
791
|
+
// The source map and the script text are relative to the start of the script, whereas
|
|
792
|
+
// raw V8 positions in inline scripts are relative to the start of the document.
|
|
793
|
+
const {lineNumber, columnNumber} =
|
|
794
|
+
script.rawLocationToRelativeLocation({lineNumber: rawLineNumber, columnNumber: rawColumnNumber});
|
|
795
|
+
|
|
759
796
|
const scopeName = sourceMap.findOriginalFunctionName({line: lineNumber, column: columnNumber});
|
|
760
797
|
if (scopeName !== null) {
|
|
761
798
|
return scopeName;
|
|
@@ -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
|
};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import '../../ui/legacy/components/data_grid/data_grid.js';
|
|
6
6
|
|
|
7
|
+
import * as Host from '../../core/host/host.js';
|
|
7
8
|
import * as i18n from '../../core/i18n/i18n.js';
|
|
8
9
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
9
10
|
import * as Lit from '../../ui/lit/lit.js';
|
|
@@ -45,6 +46,14 @@ const UIStrings = {
|
|
|
45
46
|
* @description Accessible title for the announcements data grid.
|
|
46
47
|
*/
|
|
47
48
|
ariaLiveRecordingList: 'Accessibility Announcements',
|
|
49
|
+
/**
|
|
50
|
+
* @description Context menu item for copying the announcement message text to the clipboard.
|
|
51
|
+
*/
|
|
52
|
+
copyMessage: 'Copy message',
|
|
53
|
+
/**
|
|
54
|
+
* @description Context menu item for copying the announcement element HTML snippet to the clipboard.
|
|
55
|
+
*/
|
|
56
|
+
copyElementHtml: 'Copy element HTML',
|
|
48
57
|
} as const;
|
|
49
58
|
const str_ =
|
|
50
59
|
i18n.i18n.registerUIStrings('panels/accessibility/AccessibilityAnnouncementRecordingListView.ts', UIStrings);
|
|
@@ -53,6 +62,7 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
|
53
62
|
export interface ViewInput {
|
|
54
63
|
items: readonly A11yAnnouncement[];
|
|
55
64
|
selectedItem: A11yAnnouncement|null;
|
|
65
|
+
onContextMenu: (menu: UI.ContextMenu.ContextMenu, item: A11yAnnouncement) => void;
|
|
56
66
|
onSelect: (item: A11yAnnouncement) => void;
|
|
57
67
|
onDeselect: () => void;
|
|
58
68
|
}
|
|
@@ -92,7 +102,12 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
|
|
|
92
102
|
return html`
|
|
93
103
|
<tr
|
|
94
104
|
?selected=${item === input.selectedItem}
|
|
95
|
-
@select=${() => input.onSelect(item)}
|
|
105
|
+
@select=${() => input.onSelect(item)}
|
|
106
|
+
@contextmenu=${(e: CustomEvent<UI.ContextMenu.ContextMenu>) => {
|
|
107
|
+
if (e.detail instanceof UI.ContextMenu.ContextMenu) {
|
|
108
|
+
input.onContextMenu(e.detail, item);
|
|
109
|
+
}
|
|
110
|
+
}}>
|
|
96
111
|
<td data-value=${item.time}>
|
|
97
112
|
<span>${timeString}</span>
|
|
98
113
|
</td>
|
|
@@ -159,10 +174,27 @@ export class AccessibilityAnnouncementRecordingListView extends UI.Widget.VBox {
|
|
|
159
174
|
this.requestUpdate();
|
|
160
175
|
}
|
|
161
176
|
|
|
177
|
+
#populateContextMenu(contextMenu: UI.ContextMenu.ContextMenu, item: A11yAnnouncement): void {
|
|
178
|
+
if (item.message) {
|
|
179
|
+
contextMenu.clipboardSection().appendItem(i18nString(UIStrings.copyMessage), () => {
|
|
180
|
+
Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(item.message);
|
|
181
|
+
}, {jslogContext: 'copy-message'});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (item.element) {
|
|
185
|
+
contextMenu.clipboardSection().appendItem(i18nString(UIStrings.copyElementHtml), () => {
|
|
186
|
+
Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(item.element);
|
|
187
|
+
}, {jslogContext: 'copy-element-html'});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
162
191
|
override performUpdate(): void {
|
|
163
192
|
const input: ViewInput = {
|
|
164
193
|
items: this.#items,
|
|
165
194
|
selectedItem: this.#selectedItem,
|
|
195
|
+
onContextMenu: (contextMenu: UI.ContextMenu.ContextMenu, item: A11yAnnouncement) => {
|
|
196
|
+
this.#populateContextMenu(contextMenu, item);
|
|
197
|
+
},
|
|
166
198
|
onSelect: (item: A11yAnnouncement) => {
|
|
167
199
|
this.selectedItem = item;
|
|
168
200
|
this.#onSelect?.(item);
|
|
@@ -744,14 +744,6 @@ export function validateAndSanitizeAnnouncement(payload: unknown): A11yAnnouncem
|
|
|
744
744
|
};
|
|
745
745
|
}
|
|
746
746
|
|
|
747
|
-
export function escapeCsvValue(val: string): string {
|
|
748
|
-
const escaped = val.replace(/"/g, '""');
|
|
749
|
-
if (escaped.includes(',') || escaped.includes('\n') || escaped.includes('\r') || escaped.includes('"')) {
|
|
750
|
-
return `"${escaped}"`;
|
|
751
|
-
}
|
|
752
|
-
return escaped;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
747
|
export function buildCsvContent(announcements: readonly A11yAnnouncement[]): string {
|
|
756
748
|
const csvRows: string[] = [];
|
|
757
749
|
csvRows.push(['Time', 'API', 'Politeness', 'Message'].join(','));
|
|
@@ -759,10 +751,10 @@ export function buildCsvContent(announcements: readonly A11yAnnouncement[]): str
|
|
|
759
751
|
for (const item of announcements) {
|
|
760
752
|
const timeString = new Date(item.time).toISOString();
|
|
761
753
|
const row = [
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
754
|
+
Platform.StringUtilities.escapeCsvCell(timeString),
|
|
755
|
+
Platform.StringUtilities.escapeCsvCell(item.api),
|
|
756
|
+
Platform.StringUtilities.escapeCsvCell(item.politeness),
|
|
757
|
+
Platform.StringUtilities.escapeCsvCell(item.message),
|
|
766
758
|
];
|
|
767
759
|
csvRows.push(row.join(','));
|
|
768
760
|
}
|