mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-30 01:43:36 +08:00
[sources] Fix location mapping for inline scripts
If the debugger stops in an inline script without source URL before the embedding HTML arrives, the stopping position in the source view is off. This adjusts the source positions to account for the fact that for scripts without sourceURL, v8 provides source positions in embedder coordinates, but the devtools maps to the embedded script locations. Bug: chromium:1325457 Change-Id: Ia56bec7fe1c3db678c4167a0ef01d4fffbfccada Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/4038144 Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Jaroslav Sevcik <jarin@chromium.org>
This commit is contained in:
committed by
Devtools-frontend LUCI CQ
parent
31648274e3
commit
ec855d6e98
@@ -82,7 +82,12 @@ export class DefaultScriptMapping implements DebuggerSourceMapping {
|
||||
if (!uiSourceCode) {
|
||||
return null;
|
||||
}
|
||||
const {lineNumber, columnNumber = 0} = rawLocation;
|
||||
const isInlineScriptWithoutSourceUrl = !script.hasSourceURL && script.isInlineScript();
|
||||
const lineNumber = rawLocation.lineNumber - (isInlineScriptWithoutSourceUrl ? script.lineOffset : 0);
|
||||
let columnNumber = rawLocation.columnNumber || 0;
|
||||
if (isInlineScriptWithoutSourceUrl && !lineNumber && columnNumber) {
|
||||
columnNumber -= script.columnOffset;
|
||||
}
|
||||
return uiSourceCode.uiLocation(lineNumber, columnNumber);
|
||||
}
|
||||
|
||||
@@ -92,6 +97,10 @@ export class DefaultScriptMapping implements DebuggerSourceMapping {
|
||||
if (!script) {
|
||||
return [];
|
||||
}
|
||||
if (!script.hasSourceURL && script.isInlineScript()) {
|
||||
return [this.#debuggerModel.createRawLocation(
|
||||
script, lineNumber + script.lineOffset, lineNumber ? columnNumber : columnNumber + script.columnOffset)];
|
||||
}
|
||||
return [this.#debuggerModel.createRawLocation(script, lineNumber, columnNumber)];
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,14 @@ interface LoadResult {
|
||||
errorDescription: Host.ResourceLoader.LoadErrorDescription;
|
||||
}
|
||||
|
||||
interface ScriptDescription {
|
||||
url: string;
|
||||
content: string;
|
||||
hasSourceURL?: boolean;
|
||||
startLine?: number;
|
||||
startColumn?: number;
|
||||
}
|
||||
|
||||
export class MockProtocolBackend {
|
||||
#scriptSources = new Map<string, string>();
|
||||
#sourceMapContents = new Map<string, string>();
|
||||
@@ -36,22 +44,30 @@ export class MockProtocolBackend {
|
||||
});
|
||||
}
|
||||
|
||||
async addScript(target: SDK.Target.Target, script: {url: string, content: string}, sourceMap: {
|
||||
async addScript(target: SDK.Target.Target, scriptDescription: ScriptDescription, sourceMap: {
|
||||
url: string,
|
||||
content: string,
|
||||
}|null): Promise<SDK.Script.Script> {
|
||||
const scriptId = 'SCRIPTID.' + this.#nextScriptIndex++;
|
||||
this.#scriptSources.set(scriptId, script.content);
|
||||
this.#scriptSources.set(scriptId, scriptDescription.content);
|
||||
const startLine = scriptDescription.startLine ?? 0;
|
||||
const startColumn = scriptDescription.startColumn ?? 0;
|
||||
const endLine = startLine + (scriptDescription.content.match(/^/gm)?.length ?? 1) - 1;
|
||||
let endColumn = scriptDescription.content.length - scriptDescription.content.lastIndexOf('\n') - 1;
|
||||
if (startLine === endLine) {
|
||||
endColumn += startColumn;
|
||||
}
|
||||
|
||||
dispatchEvent(target, 'Debugger.scriptParsed', {
|
||||
scriptId,
|
||||
url: script.url,
|
||||
startLine: 0,
|
||||
startColumn: 0,
|
||||
endLine: (script.content.match(/^/gm)?.length ?? 1) - 1,
|
||||
endColumn: script.content.length - script.content.lastIndexOf('\n') - 1,
|
||||
url: scriptDescription.url,
|
||||
startLine,
|
||||
startColumn,
|
||||
endLine,
|
||||
endColumn,
|
||||
executionContextId: 1,
|
||||
hash: '',
|
||||
hasSourceURL: false,
|
||||
hasSourceURL: Boolean(scriptDescription.hasSourceURL),
|
||||
sourceMapURL: sourceMap?.url ?? '',
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ ts_library("bindings") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"BreakpointManager_test.ts",
|
||||
"DefaultScriptMapping_test.ts",
|
||||
"FileUtils_test.ts",
|
||||
"IgnoreListManager_test.ts",
|
||||
"LiveLocation_test.ts",
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2022 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import * as SDK from '../../../../../front_end/core/sdk/sdk.js';
|
||||
import * as Bindings from '../../../../../front_end/models/bindings/bindings.js';
|
||||
import * as Workspace from '../../../../../front_end/models/workspace/workspace.js';
|
||||
|
||||
import type * as Platform from '../../../../../front_end/core/platform/platform.js';
|
||||
import {assertNotNullOrUndefined} from '../../../../../front_end/core/platform/platform.js';
|
||||
import {createTarget} from '../../helpers/EnvironmentHelpers.js';
|
||||
import {MockProtocolBackend} from '../../helpers/MockScopeChain.js';
|
||||
import {describeWithMockConnection} from '../../helpers/MockConnection.js';
|
||||
|
||||
describeWithMockConnection('Inline variable view scope helpers', () => {
|
||||
const url = 'file:///tmp/example.js' as Platform.DevToolsPath.UrlString;
|
||||
let target: SDK.Target.Target;
|
||||
let backend: MockProtocolBackend;
|
||||
let defaultScriptMapping: Bindings.DefaultScriptMapping.DefaultScriptMapping;
|
||||
const contentWithSourceUrl = `<p>
|
||||
Hello!
|
||||
</p>
|
||||
<script>console.log("Hi!");
|
||||
debugger;
|
||||
console.log("There!");
|
||||
//# sourceURL=test.js
|
||||
</script>`;
|
||||
const contentWithoutSourceUrl = `<p>
|
||||
Hello!
|
||||
</p>
|
||||
<script>console.log("Hi!");
|
||||
debugger;
|
||||
console.log("There!");
|
||||
</script>`;
|
||||
|
||||
beforeEach(() => {
|
||||
const workspace = Workspace.Workspace.WorkspaceImpl.instance();
|
||||
const targetManager = SDK.TargetManager.TargetManager.instance();
|
||||
const resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace);
|
||||
const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance({
|
||||
forceNew: true,
|
||||
resourceMapping,
|
||||
targetManager,
|
||||
});
|
||||
Bindings.IgnoreListManager.IgnoreListManager.instance({forceNew: true, debuggerWorkspaceBinding});
|
||||
target = createTarget();
|
||||
backend = new MockProtocolBackend();
|
||||
defaultScriptMapping = new Bindings.DefaultScriptMapping.DefaultScriptMapping(
|
||||
target.model(SDK.DebuggerModel.DebuggerModel) as SDK.DebuggerModel.DebuggerModel,
|
||||
Workspace.Workspace.WorkspaceImpl.instance(),
|
||||
Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance());
|
||||
});
|
||||
|
||||
it('maps raw locations on first line in inline scripts without sourceURL', async () => {
|
||||
const {scriptId, debuggerModel} = await backend.addScript(
|
||||
target, {content: contentWithoutSourceUrl, url, startLine: 3, startColumn: 8, hasSourceURL: false}, null);
|
||||
const uiLocation =
|
||||
defaultScriptMapping.rawLocationToUILocation(new SDK.DebuggerModel.Location(debuggerModel, scriptId, 3, 9, 0));
|
||||
assert.strictEqual(uiLocation?.lineNumber, 0);
|
||||
assert.strictEqual(uiLocation?.columnNumber, 1);
|
||||
});
|
||||
|
||||
it('maps raw locations in inline scripts without sourceURL', async () => {
|
||||
const {scriptId, debuggerModel} = await backend.addScript(
|
||||
target, {content: contentWithoutSourceUrl, url, startLine: 3, startColumn: 8, hasSourceURL: false}, null);
|
||||
const uiLocation =
|
||||
defaultScriptMapping.rawLocationToUILocation(new SDK.DebuggerModel.Location(debuggerModel, scriptId, 4, 2, 0));
|
||||
assert.strictEqual(uiLocation?.lineNumber, 1);
|
||||
assert.strictEqual(uiLocation?.columnNumber, 2);
|
||||
});
|
||||
|
||||
it('maps raw locations in inline scripts with sourceURL', async () => {
|
||||
const {scriptId, debuggerModel} = await backend.addScript(
|
||||
target, {content: contentWithSourceUrl, url, startLine: 3, startColumn: 8, hasSourceURL: true}, null);
|
||||
const uiLocation =
|
||||
defaultScriptMapping.rawLocationToUILocation(new SDK.DebuggerModel.Location(debuggerModel, scriptId, 4, 2, 0));
|
||||
assert.strictEqual(uiLocation?.lineNumber, 4);
|
||||
assert.strictEqual(uiLocation?.columnNumber, 2);
|
||||
});
|
||||
|
||||
it('maps UI locations on first line in inline scripts without sourceURL', async () => {
|
||||
const script = await backend.addScript(
|
||||
target, {content: contentWithoutSourceUrl, url, startLine: 3, startColumn: 8, hasSourceURL: false}, null);
|
||||
const uiSourceCode = uiSourceCodeFromScript(script);
|
||||
assertNotNullOrUndefined(uiSourceCode);
|
||||
|
||||
const rawLocations = defaultScriptMapping.uiLocationToRawLocations(uiSourceCode, 0, 1);
|
||||
assert.strictEqual(rawLocations.length, 1);
|
||||
assert.strictEqual(rawLocations[0].lineNumber, 3);
|
||||
assert.strictEqual(rawLocations[0].columnNumber, 9);
|
||||
});
|
||||
|
||||
it('maps UI locations in inline scripts without sourceURL', async () => {
|
||||
const script = await backend.addScript(
|
||||
target, {content: contentWithoutSourceUrl, url, startLine: 3, startColumn: 8, hasSourceURL: false}, null);
|
||||
const uiSourceCode = uiSourceCodeFromScript(script);
|
||||
assertNotNullOrUndefined(uiSourceCode);
|
||||
|
||||
const rawLocations = defaultScriptMapping.uiLocationToRawLocations(uiSourceCode, 1, 2);
|
||||
assert.strictEqual(rawLocations.length, 1);
|
||||
assert.strictEqual(rawLocations[0].lineNumber, 4);
|
||||
assert.strictEqual(rawLocations[0].columnNumber, 2);
|
||||
});
|
||||
|
||||
it('maps UI locations in inline scripts with sourceURL', async () => {
|
||||
const script = await backend.addScript(
|
||||
target, {content: contentWithSourceUrl, url, startLine: 3, startColumn: 8, hasSourceURL: true}, null);
|
||||
const uiSourceCode = uiSourceCodeFromScript(script);
|
||||
assertNotNullOrUndefined(uiSourceCode);
|
||||
|
||||
const rawLocations = defaultScriptMapping.uiLocationToRawLocations(uiSourceCode, 4, 2);
|
||||
assert.strictEqual(rawLocations.length, 1);
|
||||
assert.strictEqual(rawLocations[0].lineNumber, 4);
|
||||
assert.strictEqual(rawLocations[0].columnNumber, 2);
|
||||
});
|
||||
|
||||
function uiSourceCodeFromScript(script: SDK.Script.Script): Workspace.UISourceCode.UISourceCode|undefined {
|
||||
return defaultScriptMapping
|
||||
.rawLocationToUILocation(new SDK.DebuggerModel.Location(script.debuggerModel, script.scriptId, 0, 0, 0))
|
||||
?.uiSourceCode;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user