diff --git a/front_end/models/bindings/DefaultScriptMapping.ts b/front_end/models/bindings/DefaultScriptMapping.ts index 702ac4d798..c2bad5e3f3 100644 --- a/front_end/models/bindings/DefaultScriptMapping.ts +++ b/front_end/models/bindings/DefaultScriptMapping.ts @@ -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)]; } diff --git a/test/unittests/front_end/helpers/MockScopeChain.ts b/test/unittests/front_end/helpers/MockScopeChain.ts index 2ce611fa04..0aefd4326e 100644 --- a/test/unittests/front_end/helpers/MockScopeChain.ts +++ b/test/unittests/front_end/helpers/MockScopeChain.ts @@ -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(); #sourceMapContents = new Map(); @@ -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 { 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 ?? '', }); diff --git a/test/unittests/front_end/models/bindings/BUILD.gn b/test/unittests/front_end/models/bindings/BUILD.gn index e87bba1a08..d8578b0964 100644 --- a/test/unittests/front_end/models/bindings/BUILD.gn +++ b/test/unittests/front_end/models/bindings/BUILD.gn @@ -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", diff --git a/test/unittests/front_end/models/bindings/DefaultScriptMapping_test.ts b/test/unittests/front_end/models/bindings/DefaultScriptMapping_test.ts new file mode 100644 index 0000000000..795423394f --- /dev/null +++ b/test/unittests/front_end/models/bindings/DefaultScriptMapping_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 = `

+ Hello! +

+`; + const contentWithoutSourceUrl = `

+ Hello! +

+`; + + 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; + } +});