UNPKG

chrome-devtools-frontend

Version:
249 lines (225 loc) • 11.5 kB
// Copyright 2020 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 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 {createResource, getMainFrame} from '../../testing/ResourceTreeHelpers.js'; import * as TextUtils from '../text_utils/text_utils.js'; import * as Workspace from '../workspace/workspace.js'; import * as Bindings from './bindings.js'; const {urlString} = Platform.DevToolsPath; describeWithMockConnection('ResourceMapping', () => { let debuggerModel: SDK.DebuggerModel.DebuggerModel; let resourceMapping: Bindings.ResourceMapping.ResourceMapping; let uiSourceCode: Workspace.UISourceCode.UISourceCode; let workspace: Workspace.Workspace.WorkspaceImpl; let target: SDK.Target.Target; // This test simulates the behavior of the ResourceMapping with the // following document, which contains two inline <script>s, one with // a `//# sourceURL` annotation and one without. // // <!DOCTYPE html> // <html> // <head> // <meta charset=utf-8> // <script> // function foo() { console.log("foo"); } // foo(); // //# sourceURL=webpack:///src/foo.js // </script> // </head> // <body> // <script>console.log("bar");</script> // </body> // </html> // const url = urlString`http://example.com/index.html`; const SCRIPTS = [ { scriptId: '1' as Protocol.Runtime.ScriptId, startLine: 4, startColumn: 8, endLine: 8, endColumn: 0, sourceURL: urlString`webpack:///src/foo.js`, hasSourceURLComment: true, }, { scriptId: '2' as Protocol.Runtime.ScriptId, startLine: 11, startColumn: 8, endLine: 11, endColumn: 27, sourceURL: url, hasSourceURLComment: false, }, ]; const OTHER_SCRIPT_ID = '3' as Protocol.Runtime.ScriptId; beforeEach(async () => { target = createTarget(); const targetManager = target.targetManager(); targetManager.setScopeTarget(target); workspace = Workspace.Workspace.WorkspaceImpl.instance(); resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace); Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding.instance({forceNew: true, resourceMapping, targetManager}); Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance( {forceNew: true, resourceMapping, targetManager}); // Inject the HTML document resource. createResource(getMainFrame(target), url, 'text/html', ''); uiSourceCode = workspace.uiSourceCodeForURL(url) as Workspace.UISourceCode.UISourceCode; assert.isNotNull(uiSourceCode); // Register the inline <script>s. const hash = ''; const length = 0; const embedderName = url; const executionContextId = 1; debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel) as SDK.DebuggerModel.DebuggerModel; SCRIPTS.forEach(({scriptId, startLine, startColumn, endLine, endColumn, sourceURL, hasSourceURLComment}) => { debuggerModel.parsedScriptSource( scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, hash, undefined, false, undefined, hasSourceURLComment, false, length, false, null, null, null, null, embedderName, null); }); assert.lengthOf(debuggerModel.scripts(), SCRIPTS.length); }); it('creates UISourceCode for added target', () => { const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel)!; resourceMapping.modelRemoved(resourceTreeModel); assert.isNull(workspace.uiSourceCodeForURL(url)); resourceMapping.modelAdded(resourceTreeModel); assert.isNotNull(workspace.uiSourceCodeForURL(url)); }); it('creates UISourceCode for added out of scope target', () => { SDK.TargetManager.TargetManager.instance().setScopeTarget(null); const otherUrl = urlString`http://example.com/other.html`; createResource(getMainFrame(target), otherUrl, 'text/html', ''); uiSourceCode = workspace.uiSourceCodeForURL(otherUrl) as Workspace.UISourceCode.UISourceCode; assert.isNotNull(uiSourceCode); }); describe('uiLocationToJSLocations', () => { it('does not map locations outside of <script> tags', () => { assert.isEmpty(resourceMapping.uiLocationToJSLocations(uiSourceCode, 0, 0)); SCRIPTS.forEach(({startLine, startColumn, endLine, endColumn}) => { assert.isEmpty(resourceMapping.uiLocationToJSLocations(uiSourceCode, startLine, startColumn - 1)); assert.isEmpty(resourceMapping.uiLocationToJSLocations(uiSourceCode, endLine, endColumn)); }); assert.isEmpty(resourceMapping.uiLocationToJSLocations(uiSourceCode, 12, 1)); }); it('correctly maps inline <script> with a //# sourceURL annotation', () => { const {scriptId, startLine, startColumn, endLine, endColumn} = SCRIPTS[0]; // Debugger locations in scripts with sourceURL annotations are relative to the beginning // of the script, rather than relative to the start of the surrounding document. assert.deepEqual(resourceMapping.uiLocationToJSLocations(uiSourceCode, startLine, startColumn), [ debuggerModel.createRawLocationByScriptId(scriptId, 0, 0), ]); assert.deepEqual(resourceMapping.uiLocationToJSLocations(uiSourceCode, startLine, startColumn + 3), [ // This location does not actually exist in the simulated document, but // the ResourceMapping doesn't know (and shouldn't care) about that. debuggerModel.createRawLocationByScriptId(scriptId, 0, 3), ]); assert.deepEqual(resourceMapping.uiLocationToJSLocations(uiSourceCode, startLine + 1, 5), [ debuggerModel.createRawLocationByScriptId(scriptId, 1, 5), ]); assert.deepEqual(resourceMapping.uiLocationToJSLocations(uiSourceCode, endLine - 1, endColumn), [ debuggerModel.createRawLocationByScriptId(scriptId, endLine - startLine - 1, endColumn), ]); }); it('correctly maps inline <script> without //# sourceURL annotation', () => { const {scriptId, startLine, startColumn, endLine, endColumn} = SCRIPTS[1]; // Debugger locations in scripts without sourceURL annotations are relative to the // beginning of the surrounding document, so this is basically a 1-1 mapping. assert.strictEqual(endLine, startLine); for (let column = startColumn; column < endColumn; ++column) { assert.deepEqual(resourceMapping.uiLocationToJSLocations(uiSourceCode, startLine, column), [ debuggerModel.createRawLocationByScriptId(scriptId, startLine, column), ]); } }); }); describe('uiLocationRangeToRSLocationRanges', () => { it('correctly reports all inline <script>s when querying the whole document', () => { const rawLocationRanges = resourceMapping.uiLocationRangeToJSLocationRanges( uiSourceCode, new TextUtils.TextRange.TextRange(0, 0, 14, 0)); assert.exists(rawLocationRanges); assert.lengthOf(rawLocationRanges, SCRIPTS.length); for (let i = 0; i < SCRIPTS.length; ++i) { let {startLine, startColumn, endLine, endColumn} = SCRIPTS[i]; const {scriptId, hasSourceURLComment} = SCRIPTS[i]; const {start, end} = rawLocationRanges[i]; assert.strictEqual(start.scriptId, scriptId); assert.strictEqual(end.scriptId, scriptId); if (hasSourceURLComment) { if (endLine === startLine) { endColumn -= startColumn; } endLine -= startLine; startLine = 0; startColumn = 0; } assert.strictEqual(start.lineNumber, startLine); assert.strictEqual(start.columnNumber, startColumn); assert.strictEqual(end.lineNumber, endLine); assert.strictEqual(end.columnNumber, endColumn); } }); }); describe('jsLocationToUILocation', () => { it('does not map locations of unrelated scripts', () => { assert.isNull( resourceMapping.jsLocationToUILocation(debuggerModel.createRawLocationByScriptId(OTHER_SCRIPT_ID, 1, 1))); SCRIPTS.forEach(({startLine, startColumn, endLine, endColumn}) => { // Check that we also don't reverse map locations that overlap with the existing script locations. assert.isNull(resourceMapping.jsLocationToUILocation( debuggerModel.createRawLocationByScriptId(OTHER_SCRIPT_ID, startLine, startColumn))); assert.isNull(resourceMapping.jsLocationToUILocation( debuggerModel.createRawLocationByScriptId(OTHER_SCRIPT_ID, endLine, endColumn))); }); }); it('correctly maps inline <script> with //# sourceURL annotation', () => { const {scriptId, startLine, startColumn, endLine, endColumn} = SCRIPTS[0]; // Debugger locations in scripts with sourceURL annotations are relative to the beginning // of the script, rather than relative to the start of the surrounding document. assert.deepEqual( resourceMapping.jsLocationToUILocation(debuggerModel.createRawLocationByScriptId(scriptId, 0, 0)), new Workspace.UISourceCode.UILocation(uiSourceCode, startLine, startColumn)); assert.deepEqual( resourceMapping.jsLocationToUILocation(debuggerModel.createRawLocationByScriptId(scriptId, 0, 55)), // This location does not actually exist in the simulated document, but // the ResourceMapping doesn't know (and shouldn't care) about that. new Workspace.UISourceCode.UILocation(uiSourceCode, startLine, startColumn + 55)); assert.deepEqual( resourceMapping.jsLocationToUILocation(debuggerModel.createRawLocationByScriptId(scriptId, 2, 0)), new Workspace.UISourceCode.UILocation(uiSourceCode, startLine + 2, 0)); assert.deepEqual( resourceMapping.jsLocationToUILocation( debuggerModel.createRawLocationByScriptId(scriptId, endLine - startLine, endColumn)), new Workspace.UISourceCode.UILocation(uiSourceCode, endLine, endColumn)); }); it('correctly maps inline <script> without //# sourceURL annotation', () => { const {scriptId, startLine, startColumn, endLine, endColumn} = SCRIPTS[1]; // Debugger locations in scripts without sourceURL annotations are relative to the // beginning of the surrounding document, so this is basically a 1-1 mapping. assert.strictEqual(endLine, startLine); for (let column = startColumn; column < endColumn; ++column) { assert.deepEqual( resourceMapping.jsLocationToUILocation( debuggerModel.createRawLocationByScriptId(scriptId, startLine, column)), new Workspace.UISourceCode.UILocation(uiSourceCode, startLine, column)); } }); }); describe('getMappedLines', () => { it('reports line numbers for all inline scripts', () => { const expectedLines = new Set(); SCRIPTS.forEach(({startLine, endLine}) => { for (let line = startLine; line <= endLine; ++line) { expectedLines.add(line); } }); const mappedLines = resourceMapping.getMappedLines(uiSourceCode); assert.deepEqual(mappedLines, expectedLines); }); }); });