Skip to content

Commit 390910b

Browse files
Connor ClarkDevtools-frontend LUCI CQ
authored andcommitted
[scopes] Support inline scripts in functionBoundsAtRawLocation
Also fix a bug where the inner-most scope was always returned from ResourceScriptMapping, rather than the inner-most function. Bug: 463452667 Change-Id: Ib4d166f881661504fa50782c9a8611f54f610e46 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7205510 Auto-Submit: Connor Clark <[email protected]> Reviewed-by: Paul Irish <[email protected]> Commit-Queue: Connor Clark <[email protected]>
1 parent 512f3c2 commit 390910b

File tree

4 files changed

+114
-4
lines changed

4 files changed

+114
-4
lines changed

front_end/models/bindings/DebuggerWorkspaceBinding.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,10 +592,12 @@ class ModelData {
592592
async functionBoundsAtRawLocation(rawLocation: SDK.DebuggerModel.Location):
593593
Promise<Workspace.UISourceCode.UIFunctionBounds|null> {
594594
let scope: Workspace.UISourceCode.UIFunctionBounds|null = null;
595+
// Check source maps.
595596
scope = scope || await this.compilerMapping.functionBoundsAtRawLocation(rawLocation);
597+
// Check debugger scripts.
596598
scope = scope || await this.#resourceScriptMapping.functionBoundsAtRawLocation(rawLocation);
597-
// TODO(crbug.com/463452667): Use resourceMapping to support locations referring
598-
// to HTML inline scripts.
599+
// Check inline scripts inside HTML resources.
600+
scope = scope || await this.#resourceMapping.functionBoundsAtRawLocation(rawLocation);
599601
return scope;
600602
}
601603

front_end/models/bindings/ResourceMapping.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as Platform from '../../core/platform/platform.js';
66
import * as SDK from '../../core/sdk/sdk.js';
77
import type * as Protocol from '../../generated/protocol.js';
88
import {createTarget} from '../../testing/EnvironmentHelpers.js';
9-
import {describeWithMockConnection} from '../../testing/MockConnection.js';
9+
import {describeWithMockConnection, setMockConnectionResponseHandler} from '../../testing/MockConnection.js';
1010
import {createResource, getMainFrame} from '../../testing/ResourceTreeHelpers.js';
1111
import * as TextUtils from '../text_utils/text_utils.js';
1212
import * as Workspace from '../workspace/workspace.js';
@@ -51,6 +51,9 @@ describeWithMockConnection('ResourceMapping', () => {
5151
endColumn: 0,
5252
sourceURL: urlString`webpack:///src/foo.js`,
5353
hasSourceURLComment: true,
54+
source: `\nfunction foo() { console.log("foo"); }
55+
foo();
56+
//# sourceURL=webpack:///src/foo.js`,
5457
},
5558
{
5659
scriptId: '2' as Protocol.Runtime.ScriptId,
@@ -60,6 +63,7 @@ describeWithMockConnection('ResourceMapping', () => {
6063
endColumn: 27,
6164
sourceURL: url,
6265
hasSourceURLComment: false,
66+
source: 'console.log("bar");',
6367
},
6468
];
6569
const OTHER_SCRIPT_ID = '3' as Protocol.Runtime.ScriptId;
@@ -97,6 +101,15 @@ describeWithMockConnection('ResourceMapping', () => {
97101
undefined, hasSourceURLComment, false, length, false, null, null, null, null, embedderName, null);
98102
});
99103
assert.lengthOf(debuggerModel.scripts(), SCRIPTS.length);
104+
105+
setMockConnectionResponseHandler('Debugger.getScriptSource', param => {
106+
return {
107+
scriptSource: SCRIPTS.find(s => s.scriptId === param.scriptId)?.source ?? '',
108+
getError() {
109+
return undefined;
110+
},
111+
};
112+
});
100113
});
101114

102115
it('creates UISourceCode for added target', () => {
@@ -251,4 +264,26 @@ describeWithMockConnection('ResourceMapping', () => {
251264
assert.deepEqual(mappedLines, expectedLines);
252265
});
253266
});
267+
268+
describe('functionBoundsAtRawLocation', () => {
269+
function makeLocation(script: typeof SCRIPTS[number], line: number, column: number): SDK.DebuggerModel.Location {
270+
return new SDK.DebuggerModel.Location(debuggerModel, script.scriptId, line, column);
271+
}
272+
273+
it('finds the function bounds for an inline script', async () => {
274+
const functionBounds = await resourceMapping.functionBoundsAtRawLocation(makeLocation(SCRIPTS[0], 5, 16));
275+
assert.isOk(functionBounds);
276+
// TODO(crbug.com/452333154)
277+
// assert.strictEqual(functionBounds.name, 'foo');
278+
assert.strictEqual(functionBounds.range.startLine, 5);
279+
assert.strictEqual(functionBounds.range.startColumn, 12);
280+
assert.strictEqual(functionBounds.range.endLine, 5);
281+
assert.strictEqual(functionBounds.range.endColumn, 38);
282+
});
283+
284+
it('finds no function bounds for an inline script with no functions', async () => {
285+
const functionBounds = await resourceMapping.functionBoundsAtRawLocation(makeLocation(SCRIPTS[1], 11, 9));
286+
assert.isNotOk(functionBounds);
287+
});
288+
});
254289
});

front_end/models/bindings/ResourceMapping.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import * as Common from '../../core/common/common.js';
66
import type * as Platform from '../../core/platform/platform.js';
77
import * as SDK from '../../core/sdk/sdk.js';
8+
import * as Formatter from '../formatter/formatter.js';
89
import * as TextUtils from '../text_utils/text_utils.js';
910
import * as Workspace from '../workspace/workspace.js';
1011

@@ -269,6 +270,78 @@ export class ResourceMapping implements SDK.TargetManager.SDKModelObserver<SDK.R
269270
uiLocation.uiSourceCode.url(), uiLocation.lineNumber, uiLocation.columnNumber);
270271
}
271272

273+
async functionBoundsAtRawLocation(rawLocation: SDK.DebuggerModel.Location):
274+
Promise<Workspace.UISourceCode.UIFunctionBounds|null> {
275+
const script = rawLocation.script();
276+
if (!script) {
277+
return null;
278+
}
279+
const info = this.infoForTarget(script.debuggerModel.target());
280+
if (!info) {
281+
return null;
282+
}
283+
const embedderName = script.embedderName();
284+
if (!embedderName) {
285+
return null;
286+
}
287+
const uiSourceCode = info.getProject().uiSourceCodeForURL(embedderName);
288+
if (!uiSourceCode) {
289+
return null;
290+
}
291+
292+
let {lineNumber, columnNumber} = rawLocation;
293+
lineNumber -= script.lineOffset;
294+
if (lineNumber === 0) {
295+
columnNumber -= script.columnOffset;
296+
}
297+
298+
const scopeTreeAndText = script ? await SDK.ScopeTreeCache.scopeTreeForScript(script) : null;
299+
if (!scopeTreeAndText) {
300+
return null;
301+
}
302+
303+
// Find the inner-most scope that maps to the given position.
304+
305+
const offset = scopeTreeAndText.text.offsetFromPosition(lineNumber, columnNumber);
306+
307+
const results = [];
308+
(function walk(nodes: Formatter.FormatterWorkerPool.ScopeTreeNode[]) {
309+
for (const node of nodes) {
310+
if (!(offset >= node.start && offset < node.end)) {
311+
continue;
312+
}
313+
results.push(node);
314+
walk(node.children);
315+
}
316+
})([scopeTreeAndText.scopeTree]);
317+
318+
const result = results.findLast(
319+
node => node.kind === Formatter.FormatterWorkerPool.ScopeKind.FUNCTION ||
320+
node.kind === Formatter.FormatterWorkerPool.ScopeKind.ARROW_FUNCTION);
321+
if (!result) {
322+
return null;
323+
}
324+
325+
// Map back to positions.
326+
const startPosition = scopeTreeAndText.text.positionFromOffset(result.start);
327+
const endPosition = scopeTreeAndText.text.positionFromOffset(result.end);
328+
329+
startPosition.lineNumber += script.lineOffset;
330+
if (startPosition.lineNumber === script.lineOffset) {
331+
startPosition.columnNumber += script.columnOffset;
332+
}
333+
334+
endPosition.lineNumber += script.lineOffset;
335+
if (endPosition.lineNumber === script.lineOffset) {
336+
endPosition.columnNumber += script.columnOffset;
337+
}
338+
339+
const name = ''; // TODO(crbug.com/452333154): update ScopeVariableAnalysis to include function name.
340+
const range = new TextUtils.TextRange.TextRange(
341+
startPosition.lineNumber, startPosition.columnNumber, endPosition.lineNumber, endPosition.columnNumber);
342+
return new Workspace.UISourceCode.UIFunctionBounds(uiSourceCode, range, name);
343+
}
344+
272345
resetForTest(target: SDK.Target.Target): void {
273346
const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
274347
const info = resourceTreeModel ? this.#modelToInfo.get(resourceTreeModel) : null;

front_end/models/bindings/ResourceScriptMapping.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export class ResourceScriptMapping implements DebuggerSourceMapping {
191191

192192
const result = results.findLast(
193193
node => node.kind === Formatter.FormatterWorkerPool.ScopeKind.FUNCTION ||
194-
Formatter.FormatterWorkerPool.ScopeKind.ARROW_FUNCTION);
194+
node.kind === Formatter.FormatterWorkerPool.ScopeKind.ARROW_FUNCTION);
195195
if (!result) {
196196
return null;
197197
}

0 commit comments

Comments
 (0)