| // 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 type * as Protocol from '../../../../../front_end/generated/protocol.js'; |
| import * as Bindings from '../../../../../front_end/models/bindings/bindings.js'; |
| import * as TimelineModel from '../../../../../front_end/models/timeline_model/timeline_model.js'; |
| import * as Workspace from '../../../../../front_end/models/workspace/workspace.js'; |
| import * as Timeline from '../../../../../front_end/panels/timeline/timeline.js'; |
| import {TestPlugin} from '../../helpers/LanguagePluginHelpers.js'; |
| import {type Chrome} from '../../../../../extension-api/ExtensionAPI.js'; |
| import {assertNotNullOrUndefined} from '../../../../../front_end/core/platform/platform.js'; |
| import * as Root from '../../../../../front_end/core/root/root.js'; |
| import * as Console from '../../../../../front_end/panels/console/console.js'; |
| |
| import {createTarget} from '../../helpers/EnvironmentHelpers.js'; |
| import { |
| describeWithMockConnection, |
| dispatchEvent, |
| setMockConnectionResponseHandler, |
| } from '../../helpers/MockConnection.js'; |
| |
| const {assert} = chai; |
| |
| const SCRIPT_ID = '25'; |
| const NODE_ID = 16; |
| |
| const MINIFIED_FUNCTION_NAME = 'minified'; |
| const AUTHORED_FUNCTION_NAME = 'authored'; |
| |
| const profile = { |
| 'startTime': 151790328951, |
| 'endTime': 151791690743, |
| 'nodes': [ |
| {'callFrame': {'codeType': 'other', 'functionName': '(root)', 'scriptId': 0}, 'id': 1}, |
| {'callFrame': {'codeType': 'other', 'functionName': '(program)', 'scriptId': 0}, 'id': 2, 'parent': 1}, |
| {'callFrame': {'codeType': 'other', 'functionName': '(idle)', 'scriptId': 0}, 'id': 8, 'parent': 1}, |
| { |
| 'callFrame': { |
| 'codeType': 'JS', |
| 'columnNumber': 0, |
| 'functionName': '', |
| 'lineNumber': 0, |
| 'scriptId': SCRIPT_ID, |
| 'url': 'file://gen.js', |
| }, |
| 'id': 9, |
| 'parent': 1, |
| }, |
| { |
| 'callFrame': { |
| 'codeType': 'JS', |
| 'columnNumber': 51, |
| 'functionName': MINIFIED_FUNCTION_NAME, |
| 'lineNumber': 0, |
| 'scriptId': SCRIPT_ID, |
| 'url': 'file://gen.js', |
| }, |
| 'id': NODE_ID, |
| 'parent': 9, |
| }, |
| {'callFrame': {'codeType': 'JS', 'functionName': 'now', 'scriptId': 0}, 'id': 11, 'parent': NODE_ID}, |
| {'callFrame': {'codeType': 'other', 'functionName': '(garbage collector)', 'scriptId': 0}, 'id': 12, 'parent': 1}, |
| ], |
| 'samples': [2, 2, NODE_ID, NODE_ID, 2], |
| 'timeDeltas': [2, 2, 2, 2, 2], |
| |
| }; |
| const SCRIPT_URL = 'file://main.js'; |
| |
| // Generated with: |
| // `terser main.js --mangle --toplevel --output gen.js --source-map url='gen.js.map'` v5.15.0 |
| const SCRIPT_SOURCE = |
| 'function n(){o("hi");console.log("done")}function o(n){const o=performance.now();while(performance.now()-o<n);}n();o(200);\n//# sourceMappingURL=gen.js.map'; |
| |
| const SOURCE_MAP = { |
| version: 3, |
| names: ['sayHi', AUTHORED_FUNCTION_NAME, 'console', 'log', 'breakDuration', 'started', 'performance', 'now'], |
| sources: ['main.js'], |
| mappings: |
| 'AAAA,SAASA,IACLC,EAAW,MACXC,QAAQC,IAAI,OAChB,CAEA,SAASF,EAAWG,GAChB,MAAMC,EAAUC,YAAYC,MAC5B,MAAQD,YAAYC,MAAQF,EAAWD,GAC3C,CAEAJ,IACAC,EAAW', |
| }; |
| |
| const SOURCE_MAP_URL = 'file://gen.js.map'; |
| |
| describeWithMockConnection('Name resolving in the Performance panel', () => { |
| let performanceModel: Timeline.PerformanceModel.PerformanceModel; |
| let tracingModel: SDK.TracingModel.TracingModel; |
| let target: SDK.Target.Target; |
| beforeEach(async function() { |
| target = createTarget(); |
| performanceModel = new Timeline.PerformanceModel.PerformanceModel(); |
| const traceEvents = TimelineModel.TimelineJSProfile.TimelineJSProfileProcessor.createFakeTraceFromCpuProfile( |
| profile, 1, false, 'mock-name'); |
| tracingModel = new SDK.TracingModel.TracingModel(); |
| tracingModel.addEvents(traceEvents); |
| await performanceModel.setTracingModel(tracingModel); |
| const workspace = Workspace.Workspace.WorkspaceImpl.instance(); |
| const targetManager = SDK.TargetManager.TargetManager.instance(); |
| const resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace); |
| Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance({ |
| forceNew: true, |
| resourceMapping, |
| targetManager, |
| }); |
| SDK.PageResourceLoader.PageResourceLoader.instance({ |
| forceNew: true, |
| loadOverride: async (_: string) => ({ |
| success: true, |
| content: JSON.stringify(SOURCE_MAP), |
| errorDescription: {message: '', statusCode: 0, netError: 0, netErrorName: '', urlValid: true}, |
| }), |
| maxConcurrentLoads: 1, |
| }); |
| setMockConnectionResponseHandler('Debugger.getScriptSource', getScriptSourceHandler); |
| |
| function getScriptSourceHandler(_: Protocol.Debugger.GetScriptSourceRequest): |
| Protocol.Debugger.GetScriptSourceResponse { |
| return { |
| scriptSource: SCRIPT_SOURCE, |
| getError() { |
| return 'Unknown script'; |
| }, |
| }; |
| } |
| }); |
| |
| afterEach(async function() { |
| await Console.ConsoleView.ConsoleView.instance().getScheduledRefreshPromiseForTest(); |
| }); |
| |
| it('renames nodes from the profile models when the corresponding scripts and source maps have loaded', |
| async function() { |
| const cpuProfiles = performanceModel.timelineModel().cpuProfiles(); |
| assert.strictEqual(cpuProfiles.length, 1); |
| |
| const nodes = cpuProfiles[0].nodes(); |
| assert.strictEqual(nodes?.length, profile.nodes.length); |
| |
| const sourceMapAttachedPromise = new Promise<void>( |
| resolve => |
| performanceModel.addEventListener(Timeline.PerformanceModel.Events.NamesResolved, () => resolve())); |
| |
| const bottomModelNode = nodes?.find(n => n.id === NODE_ID); |
| |
| // Test the node's name is minified before the script and source maps load. |
| assert.strictEqual(bottomModelNode?.functionName, MINIFIED_FUNCTION_NAME); |
| |
| // Load the script and source map into the frontend. |
| dispatchEvent(target, 'Debugger.scriptParsed', { |
| scriptId: SCRIPT_ID, |
| url: SCRIPT_URL, |
| startLine: 0, |
| startColumn: 0, |
| endLine: (SCRIPT_SOURCE.match(/^/gm)?.length ?? 1) - 1, |
| endColumn: SCRIPT_SOURCE.length - SCRIPT_SOURCE.lastIndexOf('\n') - 1, |
| executionContextId: 1, |
| hash: '', |
| hasSourceURL: false, |
| sourceMapURL: SOURCE_MAP_URL, |
| }); |
| |
| await sourceMapAttachedPromise; |
| |
| // Now that the script and source map have loaded, test that the model has been automatically |
| // reparsed to resolve function names. |
| assert.strictEqual(bottomModelNode?.functionName, AUTHORED_FUNCTION_NAME); |
| }); |
| |
| it('resolves function names using a plugin when available', async () => { |
| const PLUGIN_FUNCTION_NAME = 'PLUGIN_FUNCTION_NAME'; |
| class Plugin extends TestPlugin { |
| constructor() { |
| super('InstrumentationBreakpoints'); |
| } |
| |
| override getFunctionInfo(_rawLocation: Chrome.DevTools.RawLocation): |
| Promise<{frames: Chrome.DevTools.FunctionInfo[], missingSymbolFiles?: string[]|undefined}> { |
| return Promise.resolve({frames: [{name: PLUGIN_FUNCTION_NAME}]}); |
| } |
| override handleScript(_: SDK.Script.Script) { |
| return true; |
| } |
| } |
| |
| const cpuProfiles = performanceModel.timelineModel().cpuProfiles(); |
| const nodes = cpuProfiles[0].nodes(); |
| |
| Root.Runtime.experiments.setEnabled('wasmDWARFDebugging', true); |
| const pluginManager = |
| Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().initPluginManagerForTest(); |
| assertNotNullOrUndefined(pluginManager); |
| pluginManager.addPlugin(new Plugin()); |
| |
| const namesResolvedPromise = new Promise<void>( |
| resolve => performanceModel.addEventListener(Timeline.PerformanceModel.Events.NamesResolved, () => resolve())); |
| |
| const bottomModelNode = nodes?.find(n => n.id === NODE_ID); |
| |
| // Load the script into the frontend. |
| dispatchEvent(target, 'Debugger.scriptParsed', { |
| scriptId: SCRIPT_ID, |
| url: SCRIPT_URL, |
| startLine: 0, |
| startColumn: 0, |
| endLine: (SCRIPT_SOURCE.match(/^/gm)?.length ?? 1) - 1, |
| endColumn: SCRIPT_SOURCE.length - SCRIPT_SOURCE.lastIndexOf('\n') - 1, |
| executionContextId: 1, |
| hash: '', |
| hasSourceURL: false, |
| sourceMapURL: SOURCE_MAP_URL, |
| }); |
| await namesResolvedPromise; |
| |
| assert.strictEqual(bottomModelNode?.functionName, PLUGIN_FUNCTION_NAME); |
| }); |
| }); |