| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as Platform from '../../core/platform/platform.js'; |
| import * as SDK from '../../core/sdk/sdk.js'; |
| import type * as Protocol from '../../generated/protocol.js'; |
| import {createTarget} from '../../testing/EnvironmentHelpers.js'; |
| import {describeWithMockConnection} from '../../testing/MockConnection.js'; |
| import {MockProtocolBackend} from '../../testing/MockScopeChain.js'; |
| import {setMockResourceTree} from '../../testing/ResourceTreeHelpers.js'; |
| import {loadBasicSourceMapExample} from '../../testing/SourceMapHelpers.js'; |
| import {protocolCallFrame, stringifyStackTrace} from '../../testing/StackTraceHelpers.js'; |
| import * as Workspace from '../workspace/workspace.js'; |
| |
| import * as Bindings from './bindings.js'; |
| |
| const {urlString} = Platform.DevToolsPath; |
| |
| describeWithMockConnection('DebuggerWorkspaceBinding', () => { |
| let debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding; |
| |
| beforeEach(() => { |
| const targetManager = SDK.TargetManager.TargetManager.instance(); |
| const workspace = Workspace.Workspace.WorkspaceImpl.instance(); |
| const resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace); |
| const ignoreListManager = Workspace.IgnoreListManager.IgnoreListManager.instance({forceNew: true}); |
| debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance({ |
| forceNew: true, |
| resourceMapping, |
| targetManager, |
| ignoreListManager, |
| workspace, |
| }); |
| setMockResourceTree(false); |
| }); |
| |
| it('can wait for a uiSourceCode if it is not yet available', async () => { |
| const backend = new MockProtocolBackend(); |
| const target = createTarget({id: 'main' as Protocol.Target.TargetID, name: 'main', type: SDK.Target.Type.FRAME}); |
| SDK.TargetManager.TargetManager.instance().setScopeTarget(target); |
| const scriptUrl = urlString`http://script-host/script.js`; |
| const scriptInfo = {url: scriptUrl, content: 'console.log(1);', startLine: 0, startColumn: 0, hasSourceURL: false}; |
| |
| // Create a second target. |
| const workerTarget = createTarget({ |
| id: 'worker' as Protocol.Target.TargetID, |
| name: 'worker', |
| type: SDK.Target.Type.ServiceWorker, |
| parentTarget: target, |
| }); |
| |
| // Before any script is registered, there shouldn't be any uiSourceCodes. |
| assert.isNull(Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(scriptUrl)); |
| |
| // Create promise to await the uiSourceCode given the url and its target. |
| const uiSourceCodePromise = debuggerWorkspaceBinding.waitForUISourceCodeAdded(scriptUrl, target); |
| |
| // Register the script, which will kick off creating the uiSourceCode. |
| await backend.addScript(target, scriptInfo, null); |
| await backend.addScript(workerTarget, scriptInfo, null); |
| |
| // Await the promise to retrieve the uiSourceCode. |
| const uiSourceCode = await uiSourceCodePromise; |
| |
| // Check if the uiSourceCode is the expected one (from the main target, and having the correct sourceURL). |
| assert.strictEqual(uiSourceCode.url(), scriptUrl); |
| assert.deepEqual(Bindings.NetworkProject.NetworkProject.targetForUISourceCode(uiSourceCode), target); |
| }); |
| |
| it('augments sourcemap with scopes via DebuggerWorkspaceBindings.setFunctionRanges', async () => { |
| const target = createTarget({id: 'main' as Protocol.Target.TargetID, name: 'main', type: SDK.Target.Type.FRAME}); |
| const validFunctionRanges = [{start: {line: 0, column: 0}, end: {line: 10, column: 1}, name: 'foo'}]; |
| const debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel); |
| assert.exists(debuggerModel); |
| |
| const script = (await loadBasicSourceMapExample(target)).script; |
| const sourceMap = await debuggerModel.sourceMapManager().sourceMapForClientPromise(script); |
| |
| assert.exists(sourceMap); |
| const url: string = sourceMap.url(); |
| assert.strictEqual(url, 'file://gen.js.map/'); |
| |
| const uiSourceCodeForSourceMap = |
| Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(sourceMap.sourceURLs()[0]); |
| assert.exists(uiSourceCodeForSourceMap); |
| |
| Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().setFunctionRanges( |
| uiSourceCodeForSourceMap, validFunctionRanges); |
| |
| assert.isTrue(sourceMap.hasScopeInfo()); |
| assert.strictEqual(sourceMap.findOriginalFunctionName({line: 0, column: 110}), 'foo'); |
| }); |
| |
| describe('createStackTraceFromProtocolRuntime', () => { |
| it('identity translates frames by default', async () => { |
| const target = createTarget({id: 'main' as Protocol.Target.TargetID, name: 'main', type: SDK.Target.Type.FRAME}); |
| const stackTrace = await debuggerWorkspaceBinding.createStackTraceFromProtocolRuntime( |
| { |
| callFrames: [ |
| 'foo.js:1:foo:1:10', |
| 'bar.js:2:bar:2:20', |
| 'baz.js:3:baz:3:30', |
| ].map(protocolCallFrame), |
| }, |
| target); |
| |
| assert.strictEqual(stringifyStackTrace(stackTrace), [ |
| 'at foo (foo.js:1:10)', |
| 'at bar (bar.js:2:20)', |
| 'at baz (baz.js:3:30)', |
| ].join('\n')); |
| }); |
| |
| it('identity translates frames for disposed targets (no ModelData instance)', async () => { |
| const target = createTarget({id: 'main' as Protocol.Target.TargetID, name: 'main', type: SDK.Target.Type.FRAME}); |
| target.dispose('disposed for testing'); |
| const stackTrace = await debuggerWorkspaceBinding.createStackTraceFromProtocolRuntime( |
| { |
| callFrames: [ |
| 'foo.js:1:foo:1:10', |
| 'bar.js:2:bar:2:20', |
| 'baz.js:3:baz:3:30', |
| ].map(protocolCallFrame), |
| }, |
| target); |
| |
| assert.strictEqual(stringifyStackTrace(stackTrace), [ |
| 'at foo (foo.js:1:10)', |
| 'at bar (bar.js:2:20)', |
| 'at baz (baz.js:3:30)', |
| ].join('\n')); |
| }); |
| |
| it('calls the debugger language plugin', async () => { |
| const target = createTarget({id: 'main' as Protocol.Target.TargetID, name: 'main', type: SDK.Target.Type.FRAME}); |
| const spy = sinon.spy(debuggerWorkspaceBinding.pluginManager, 'translateRawFramesStep'); |
| |
| await debuggerWorkspaceBinding.createStackTraceFromProtocolRuntime( |
| { |
| callFrames: [ |
| 'foo.js:1:foo:1:10', |
| 'bar.js:2:bar:2:20', |
| 'baz.js:3:baz:3:30', |
| ].map(protocolCallFrame), |
| }, |
| target); |
| |
| sinon.assert.calledThrice(spy); |
| }); |
| |
| it('translates source location via the fallback script mapping', async () => { |
| const backend = new MockProtocolBackend(); |
| const target = createTarget({id: 'main' as Protocol.Target.TargetID, name: 'main', type: SDK.Target.Type.FRAME}); |
| const script = await backend.addScript( |
| target, { |
| url: Platform.DevToolsPath.urlString`http://example.com/foo.js`, |
| content: '// content omitted as its not required', |
| }, |
| null); |
| const uiSourceCode = debuggerWorkspaceBinding.uiSourceCodeForScript(script); |
| assert.exists(uiSourceCode); |
| |
| const stackTrace = await debuggerWorkspaceBinding.createStackTraceFromProtocolRuntime( |
| { |
| callFrames: [ |
| `${script.contentURL()}:${script.scriptId}:foo:1:10`, |
| `${script.contentURL()}:${script.scriptId}:bar:2:20`, |
| `${script.contentURL()}:${script.scriptId}:baz:3:30`, |
| ].map(protocolCallFrame), |
| }, |
| target); |
| |
| assert.strictEqual(stringifyStackTrace(stackTrace), [ |
| 'at foo (foo.js:1:10)', |
| 'at bar (foo.js:2:20)', |
| 'at baz (foo.js:3:30)', |
| ].join('\n')); |
| |
| assert.strictEqual(stackTrace.syncFragment.frames[0].uiSourceCode, uiSourceCode); |
| assert.strictEqual(stackTrace.syncFragment.frames[1].uiSourceCode, uiSourceCode); |
| assert.strictEqual(stackTrace.syncFragment.frames[2].uiSourceCode, uiSourceCode); |
| }); |
| }); |
| }); |