mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-09 13:18:29 +08:00
Following up on https://crrev.com/c/2666690, this enables support for the following features in DevTools front-end: - Conditional breakpoints - Logpoints - Never pause here All of these are based on conditional breakpoints support in V8, which are now supported in V8 for Wasm. The condition is always a JavaScript snippet that is executed via "Local Debug-Evaluate", and hence has access to the state of the Wasm engine via the Debug Proxy API. Note that for sources provided via language plugins we keep the relevant context menus hidden, since it's not yet clear how we'd go about supporting expression evaluation there (i.e. we will need some kind of basic C++ expression support in case of the DWARF extension). Fixed: chromium:1173006 Bug: chromium:1164241, chromium:1071432, chromium:1142275 Change-Id: Ib26875f38f1f26388d3ee772e95424fdc17e5eb9 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2666696 Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Philip Pfaffe <pfaffe@chromium.org> Auto-Submit: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Philip Pfaffe <pfaffe@chromium.org>
721 lines
24 KiB
JavaScript
721 lines
24 KiB
JavaScript
// Copyright 2014 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 Common from '../common/common.js'; // eslint-disable-line no-unused-vars
|
|
import * as Platform from '../platform/platform.js';
|
|
import * as Root from '../root/root.js';
|
|
import * as SDK from '../sdk/sdk.js';
|
|
import * as Workspace from '../workspace/workspace.js'; // eslint-disable-line no-unused-vars
|
|
|
|
import {CompilerScriptMapping} from './CompilerScriptMapping.js';
|
|
import {DebuggerLanguagePluginManager} from './DebuggerLanguagePlugins.js';
|
|
import {DefaultScriptMapping} from './DefaultScriptMapping.js';
|
|
import {IgnoreListManager} from './IgnoreListManager.js';
|
|
import {LiveLocation, LiveLocationPool, LiveLocationWithPool} from './LiveLocation.js'; // eslint-disable-line no-unused-vars
|
|
import {ResourceMapping} from './ResourceMapping.js';
|
|
import {ResourceScriptFile, ResourceScriptMapping} from './ResourceScriptMapping.js'; // eslint-disable-line no-unused-vars
|
|
|
|
/**
|
|
* @type {!DebuggerWorkspaceBinding}
|
|
*/
|
|
let debuggerWorkspaceBindingInstance;
|
|
|
|
/**
|
|
* @implements {SDK.SDKModel.SDKModelObserver<!SDK.DebuggerModel.DebuggerModel>}
|
|
*/
|
|
export class DebuggerWorkspaceBinding {
|
|
/**
|
|
* @private
|
|
* @param {!SDK.SDKModel.TargetManager} targetManager
|
|
* @param {!Workspace.Workspace.WorkspaceImpl} workspace
|
|
*/
|
|
constructor(targetManager, workspace) {
|
|
this._workspace = workspace;
|
|
|
|
/** @type {!Array<!DebuggerSourceMapping>} */
|
|
this._sourceMappings = [];
|
|
|
|
/** @type {!Map.<!SDK.DebuggerModel.DebuggerModel, !ModelData>} */
|
|
this._debuggerModelToData = new Map();
|
|
targetManager.addModelListener(
|
|
SDK.DebuggerModel.DebuggerModel, SDK.DebuggerModel.Events.GlobalObjectCleared, this._globalObjectCleared, this);
|
|
targetManager.addModelListener(
|
|
SDK.DebuggerModel.DebuggerModel, SDK.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this);
|
|
targetManager.observeModels(SDK.DebuggerModel.DebuggerModel, this);
|
|
|
|
/** @type {!Set.<!Promise<?>>} */
|
|
this._liveLocationPromises = new Set();
|
|
|
|
this.pluginManager = Root.Runtime.experiments.isEnabled('wasmDWARFDebugging') ?
|
|
new DebuggerLanguagePluginManager(targetManager, workspace, this) :
|
|
null;
|
|
}
|
|
|
|
/**
|
|
* @param {{forceNew: ?boolean, targetManager: ?SDK.SDKModel.TargetManager, workspace: ?Workspace.Workspace.WorkspaceImpl}} opts
|
|
*/
|
|
static instance(opts = {forceNew: null, targetManager: null, workspace: null}) {
|
|
const {forceNew, targetManager, workspace} = opts;
|
|
if (!debuggerWorkspaceBindingInstance || forceNew) {
|
|
if (!targetManager || !workspace) {
|
|
throw new Error(`Unable to create DebuggerWorkspaceBinding: targetManager and workspace must be provided: ${
|
|
new Error().stack}`);
|
|
}
|
|
|
|
debuggerWorkspaceBindingInstance = new DebuggerWorkspaceBinding(targetManager, workspace);
|
|
}
|
|
|
|
return debuggerWorkspaceBindingInstance;
|
|
}
|
|
|
|
/**
|
|
* @param {!DebuggerSourceMapping} sourceMapping
|
|
*/
|
|
addSourceMapping(sourceMapping) {
|
|
this._sourceMappings.push(sourceMapping);
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.StepMode} mode
|
|
* @param {!SDK.DebuggerModel.CallFrame} callFrame
|
|
* @return {!Promise<!Array<!{start:!SDK.DebuggerModel.Location, end:!SDK.DebuggerModel.Location}>>}
|
|
*/
|
|
async _computeAutoStepRanges(mode, callFrame) {
|
|
/**
|
|
* @param {!SDK.DebuggerModel.Location} location
|
|
* @param {!{start:!SDK.DebuggerModel.Location, end:!SDK.DebuggerModel.Location}} range
|
|
* @return {boolean}
|
|
*/
|
|
function contained(location, range) {
|
|
const {start, end} = range;
|
|
if (start.scriptId !== location.scriptId) {
|
|
return false;
|
|
}
|
|
if (location.lineNumber < start.lineNumber || location.lineNumber > end.lineNumber) {
|
|
return false;
|
|
}
|
|
if (location.lineNumber === start.lineNumber && location.columnNumber < start.columnNumber) {
|
|
return false;
|
|
}
|
|
if (location.lineNumber === end.lineNumber && location.columnNumber >= end.columnNumber) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// TODO(crbug.com/1018234): Also take into account source maps here and remove the auto-stepping
|
|
// logic in the front-end (which is currently still an experiment) completely.
|
|
const pluginManager = this.pluginManager;
|
|
if (pluginManager) {
|
|
const rawLocation = callFrame.location();
|
|
if (mode === SDK.DebuggerModel.StepMode.StepOut) {
|
|
// Step out of inline function.
|
|
return await pluginManager.getInlinedFunctionRanges(rawLocation);
|
|
}
|
|
/** @type {!Array<!{start:!SDK.DebuggerModel.Location, end:!SDK.DebuggerModel.Location}>} */
|
|
let ranges = [];
|
|
const uiLocation = await pluginManager.rawLocationToUILocation(rawLocation);
|
|
if (uiLocation) {
|
|
ranges = await pluginManager.uiLocationToRawLocationRanges(
|
|
uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber) ||
|
|
[];
|
|
// TODO(bmeurer): Remove the {rawLocation} from the {ranges}?
|
|
ranges = ranges.filter(range => contained(rawLocation, range));
|
|
}
|
|
if (mode === SDK.DebuggerModel.StepMode.StepOver) {
|
|
// Step over an inlined function.
|
|
ranges = ranges.concat(await pluginManager.getInlinedCalleesRanges(rawLocation));
|
|
}
|
|
return ranges;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel
|
|
*/
|
|
modelAdded(debuggerModel) {
|
|
this._debuggerModelToData.set(debuggerModel, new ModelData(debuggerModel, this));
|
|
debuggerModel.setComputeAutoStepRangesCallback(this._computeAutoStepRanges.bind(this));
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel
|
|
*/
|
|
modelRemoved(debuggerModel) {
|
|
debuggerModel.setComputeAutoStepRangesCallback(null);
|
|
const modelData = this._debuggerModelToData.get(debuggerModel);
|
|
if (modelData) {
|
|
modelData._dispose();
|
|
this._debuggerModelToData.delete(debuggerModel);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The promise returned by this function is resolved once all *currently*
|
|
* pending LiveLocations are processed.
|
|
*
|
|
* @return {!Promise<?>}
|
|
*/
|
|
async pendingLiveLocationChangesPromise() {
|
|
await Promise.all(this._liveLocationPromises);
|
|
}
|
|
|
|
/**
|
|
* @param {!Promise<?>} promise
|
|
*/
|
|
_recordLiveLocationChange(promise) {
|
|
promise.then(() => {
|
|
this._liveLocationPromises.delete(promise);
|
|
});
|
|
this._liveLocationPromises.add(promise);
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.Script.Script} script
|
|
*/
|
|
async updateLocations(script) {
|
|
const modelData = this._debuggerModelToData.get(script.debuggerModel);
|
|
if (modelData) {
|
|
const updatePromise = modelData._updateLocations(script);
|
|
this._recordLiveLocationChange(updatePromise);
|
|
await updatePromise;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.Location} rawLocation
|
|
* @param {function(!LiveLocation): !Promise<?>} updateDelegate
|
|
* @param {!LiveLocationPool} locationPool
|
|
* @return {!Promise<?Location>}
|
|
*/
|
|
async createLiveLocation(rawLocation, updateDelegate, locationPool) {
|
|
const modelData = this._debuggerModelToData.get(rawLocation.debuggerModel);
|
|
if (!modelData) {
|
|
return null;
|
|
}
|
|
const liveLocationPromise = modelData._createLiveLocation(rawLocation, updateDelegate, locationPool);
|
|
this._recordLiveLocationChange(liveLocationPromise);
|
|
return liveLocationPromise;
|
|
}
|
|
|
|
/**
|
|
* @param {!Array<!SDK.DebuggerModel.Location>} rawLocations
|
|
* @param {function(!LiveLocation): !Promise<?>} updateDelegate
|
|
* @param {!LiveLocationPool} locationPool
|
|
* @return {!Promise<!LiveLocation>}
|
|
*/
|
|
async createStackTraceTopFrameLiveLocation(rawLocations, updateDelegate, locationPool) {
|
|
console.assert(rawLocations.length > 0);
|
|
const locationPromise =
|
|
StackTraceTopFrameLocation.createStackTraceTopFrameLocation(rawLocations, this, updateDelegate, locationPool);
|
|
this._recordLiveLocationChange(locationPromise);
|
|
return locationPromise;
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.Location} location
|
|
* @param {function(!LiveLocation): !Promise<?>} updateDelegate
|
|
* @param {!LiveLocationPool} locationPool
|
|
* @return {!Promise<?Location>}
|
|
*/
|
|
async createCallFrameLiveLocation(location, updateDelegate, locationPool) {
|
|
const script = location.script();
|
|
if (!script) {
|
|
return null;
|
|
}
|
|
const debuggerModel = location.debuggerModel;
|
|
const liveLocationPromise = this.createLiveLocation(location, updateDelegate, locationPool);
|
|
this._recordLiveLocationChange(liveLocationPromise);
|
|
const liveLocation = await liveLocationPromise;
|
|
if (!liveLocation) {
|
|
return null;
|
|
}
|
|
this._registerCallFrameLiveLocation(debuggerModel, liveLocation);
|
|
return liveLocation;
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.Location} rawLocation
|
|
* @return {!Promise<?Workspace.UISourceCode.UILocation>}
|
|
*/
|
|
async rawLocationToUILocation(rawLocation) {
|
|
for (const sourceMapping of this._sourceMappings) {
|
|
const uiLocation = sourceMapping.rawLocationToUILocation(rawLocation);
|
|
if (uiLocation) {
|
|
return uiLocation;
|
|
}
|
|
}
|
|
if (this.pluginManager) {
|
|
const uiLocation = await this.pluginManager.rawLocationToUILocation(rawLocation);
|
|
if (uiLocation) {
|
|
return uiLocation;
|
|
}
|
|
}
|
|
const modelData = this._debuggerModelToData.get(rawLocation.debuggerModel);
|
|
return modelData ? modelData._rawLocationToUILocation(rawLocation) : null;
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel
|
|
* @param {string} url
|
|
* @param {boolean} isContentScript
|
|
*/
|
|
uiSourceCodeForSourceMapSourceURL(debuggerModel, url, isContentScript) {
|
|
const modelData = this._debuggerModelToData.get(debuggerModel);
|
|
if (!modelData) {
|
|
return null;
|
|
}
|
|
return modelData._compilerMapping.uiSourceCodeForURL(url, isContentScript);
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
|
|
* @param {number} lineNumber
|
|
* @param {number=} columnNumber
|
|
* @return {!Promise<!Array<!SDK.DebuggerModel.Location>>}
|
|
*/
|
|
async uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber) {
|
|
for (const sourceMapping of this._sourceMappings) {
|
|
const locations = sourceMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber);
|
|
if (locations.length) {
|
|
return locations;
|
|
}
|
|
}
|
|
// TODO(bmeurer): This is more complicated than it needs to be, because
|
|
// only the pluginManager part below needs to be asynchronous and any
|
|
// given uiSourceCode cannot be provided by both a plugin and another
|
|
// mean of source mapping. Yet, there's currently a subtle timing issue
|
|
// with http/tests/devtools/sources/debugger-ui/click-gutter-breakpoint.js
|
|
// and so for now we leave the promises in here.
|
|
const locationPromises = [];
|
|
if (this.pluginManager) {
|
|
locationPromises.push(this.pluginManager.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber)
|
|
.then(locations => locations || []));
|
|
}
|
|
for (const modelData of this._debuggerModelToData.values()) {
|
|
locationPromises.push(
|
|
Promise.resolve(modelData._uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber)));
|
|
}
|
|
return (await Promise.all(locationPromises)).flat();
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
|
|
* @param {number} lineNumber
|
|
* @param {number} columnNumber
|
|
* @return {!Array<!SDK.DebuggerModel.Location>}
|
|
*/
|
|
uiLocationToRawLocationsForUnformattedJavaScript(uiSourceCode, lineNumber, columnNumber) {
|
|
console.assert(uiSourceCode.contentType().isScript());
|
|
const locations = [];
|
|
for (const modelData of this._debuggerModelToData.values()) {
|
|
locations.push(...modelData._uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber));
|
|
}
|
|
return locations;
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UILocation} uiLocation
|
|
* @return {!Promise<!Workspace.UISourceCode.UILocation>}
|
|
*/
|
|
async normalizeUILocation(uiLocation) {
|
|
const rawLocations =
|
|
await this.uiLocationToRawLocations(uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber);
|
|
for (const location of rawLocations) {
|
|
const uiLocationCandidate = await this.rawLocationToUILocation(location);
|
|
if (uiLocationCandidate) {
|
|
return uiLocationCandidate;
|
|
}
|
|
}
|
|
return uiLocation;
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
|
|
* @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel
|
|
* @return {?ResourceScriptFile}
|
|
*/
|
|
scriptFile(uiSourceCode, debuggerModel) {
|
|
const modelData = this._debuggerModelToData.get(debuggerModel);
|
|
return modelData ? modelData._resourceMapping.scriptFile(uiSourceCode) : null;
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
|
|
* @return {!Array<!SDK.Script.Script>}
|
|
*/
|
|
scriptsForUISourceCode(uiSourceCode) {
|
|
const scripts = new Set();
|
|
if (this.pluginManager) {
|
|
this.pluginManager.scriptsForUISourceCode(uiSourceCode).forEach(script => scripts.add(script));
|
|
}
|
|
for (const modelData of this._debuggerModelToData.values()) {
|
|
const resourceScriptFile = modelData._resourceMapping.scriptFile(uiSourceCode);
|
|
if (resourceScriptFile && resourceScriptFile._script) {
|
|
scripts.add(resourceScriptFile._script);
|
|
}
|
|
modelData._compilerMapping.scriptsForUISourceCode(uiSourceCode).forEach(script => scripts.add(script));
|
|
}
|
|
return [...scripts];
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
|
|
* @return {boolean}
|
|
*/
|
|
supportsConditionalBreakpoints(uiSourceCode) {
|
|
// DevTools traditionally supported (JavaScript) conditions
|
|
// for breakpoints everywhere, so we keep that behavior...
|
|
if (!this.pluginManager) {
|
|
return true;
|
|
}
|
|
const scripts = this.pluginManager.scriptsForUISourceCode(uiSourceCode);
|
|
return scripts.every(script => script.isJavaScript());
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.Script.Script} script
|
|
* @return {?SDK.SourceMap.SourceMap}
|
|
*/
|
|
sourceMapForScript(script) {
|
|
const modelData = this._debuggerModelToData.get(script.debuggerModel);
|
|
if (!modelData) {
|
|
return null;
|
|
}
|
|
return modelData._compilerMapping.sourceMapForScript(script);
|
|
}
|
|
|
|
/**
|
|
* @param {!Common.EventTarget.EventTargetEvent} event
|
|
*/
|
|
_globalObjectCleared(event) {
|
|
const debuggerModel = /** @type {!SDK.DebuggerModel.DebuggerModel} */ (event.data);
|
|
this._reset(debuggerModel);
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel
|
|
*/
|
|
_reset(debuggerModel) {
|
|
const modelData = this._debuggerModelToData.get(debuggerModel);
|
|
if (!modelData) {
|
|
return;
|
|
}
|
|
for (const location of modelData.callFrameLocations.values()) {
|
|
this._removeLiveLocation(location);
|
|
}
|
|
modelData.callFrameLocations.clear();
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.SDKModel.Target} target
|
|
*/
|
|
_resetForTest(target) {
|
|
const debuggerModel =
|
|
/** @type {!SDK.DebuggerModel.DebuggerModel} */ (target.model(SDK.DebuggerModel.DebuggerModel));
|
|
const modelData = this._debuggerModelToData.get(debuggerModel);
|
|
if (modelData) {
|
|
modelData._resourceMapping.resetForTest();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel
|
|
* @param {!Location} location
|
|
*/
|
|
_registerCallFrameLiveLocation(debuggerModel, location) {
|
|
const modelData = this._debuggerModelToData.get(debuggerModel);
|
|
if (modelData) {
|
|
const locations = modelData.callFrameLocations;
|
|
locations.add(location);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Location} location
|
|
*/
|
|
_removeLiveLocation(location) {
|
|
const modelData = this._debuggerModelToData.get(location._rawLocation.debuggerModel);
|
|
if (modelData) {
|
|
modelData._disposeLocation(location);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Common.EventTarget.EventTargetEvent} event
|
|
*/
|
|
_debuggerResumed(event) {
|
|
const debuggerModel = /** @type {!SDK.DebuggerModel.DebuggerModel} */ (event.data);
|
|
this._reset(debuggerModel);
|
|
}
|
|
}
|
|
|
|
class ModelData {
|
|
/**
|
|
* @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel
|
|
* @param {!DebuggerWorkspaceBinding} debuggerWorkspaceBinding
|
|
*/
|
|
constructor(debuggerModel, debuggerWorkspaceBinding) {
|
|
this._debuggerModel = debuggerModel;
|
|
this._debuggerWorkspaceBinding = debuggerWorkspaceBinding;
|
|
|
|
/** @type {!Set.<!Location>} */
|
|
this.callFrameLocations = new Set();
|
|
|
|
const workspace = debuggerWorkspaceBinding._workspace;
|
|
this._defaultMapping = new DefaultScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding);
|
|
this._resourceMapping = new ResourceScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding);
|
|
this._compilerMapping = new CompilerScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding);
|
|
|
|
/** @type {!Platform.MapUtilities.Multimap<string, !Location>} */
|
|
this._locations = new Platform.MapUtilities.Multimap();
|
|
|
|
debuggerModel.setBeforePausedCallback(this._beforePaused.bind(this));
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.Location} rawLocation
|
|
* @param {function(!LiveLocation): !Promise<?>} updateDelegate
|
|
* @param {!LiveLocationPool} locationPool
|
|
* @return {!Promise<!Location>}
|
|
*/
|
|
async _createLiveLocation(rawLocation, updateDelegate, locationPool) {
|
|
console.assert(rawLocation.scriptId !== '');
|
|
const scriptId = rawLocation.scriptId;
|
|
const location = new Location(scriptId, rawLocation, this._debuggerWorkspaceBinding, updateDelegate, locationPool);
|
|
this._locations.set(scriptId, location);
|
|
await location.update();
|
|
return location;
|
|
}
|
|
|
|
/**
|
|
* @param {!Location} location
|
|
*/
|
|
_disposeLocation(location) {
|
|
this._locations.delete(location._scriptId, location);
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.Script.Script} script
|
|
*/
|
|
async _updateLocations(script) {
|
|
const promises = [];
|
|
for (const location of this._locations.get(script.scriptId)) {
|
|
promises.push(location.update());
|
|
}
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.Location} rawLocation
|
|
* @return {?Workspace.UISourceCode.UILocation}
|
|
*/
|
|
_rawLocationToUILocation(rawLocation) {
|
|
let uiLocation = this._compilerMapping.rawLocationToUILocation(rawLocation);
|
|
uiLocation = uiLocation || this._resourceMapping.rawLocationToUILocation(rawLocation);
|
|
uiLocation = uiLocation || ResourceMapping.instance().jsLocationToUILocation(rawLocation);
|
|
uiLocation = uiLocation || this._defaultMapping.rawLocationToUILocation(rawLocation);
|
|
return /** @type {!Workspace.UISourceCode.UILocation} */ (uiLocation);
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
|
|
* @param {number} lineNumber
|
|
* @param {number=} columnNumber
|
|
* @return {!Array<!SDK.DebuggerModel.Location>}
|
|
*/
|
|
_uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber = 0) {
|
|
// TODO(crbug.com/1153123): Revisit the `columnNumber = 0` and also preserve `undefined` for source maps?
|
|
let locations = this._compilerMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber);
|
|
locations = locations.length ?
|
|
locations :
|
|
this._resourceMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber);
|
|
locations = locations.length ?
|
|
locations :
|
|
ResourceMapping.instance().uiLocationToJSLocations(uiSourceCode, lineNumber, columnNumber);
|
|
locations = locations.length ?
|
|
locations :
|
|
this._defaultMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber);
|
|
return locations;
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.DebuggerPausedDetails} debuggerPausedDetails
|
|
* @return {boolean}
|
|
*/
|
|
_beforePaused(debuggerPausedDetails) {
|
|
const callFrame = debuggerPausedDetails.callFrames[0];
|
|
if (!callFrame) {
|
|
return false;
|
|
}
|
|
if (!Root.Runtime.experiments.isEnabled('emptySourceMapAutoStepping')) {
|
|
return true;
|
|
}
|
|
return Boolean(this._compilerMapping.mapsToSourceCode(callFrame.location()));
|
|
}
|
|
|
|
_dispose() {
|
|
this._debuggerModel.setBeforePausedCallback(null);
|
|
this._compilerMapping.dispose();
|
|
this._resourceMapping.dispose();
|
|
this._defaultMapping.dispose();
|
|
}
|
|
}
|
|
|
|
export class Location extends LiveLocationWithPool {
|
|
/**
|
|
* @param {string} scriptId
|
|
* @param {!SDK.DebuggerModel.Location} rawLocation
|
|
* @param {!DebuggerWorkspaceBinding} binding
|
|
* @param {function(!LiveLocation): !Promise<?>} updateDelegate
|
|
* @param {!LiveLocationPool} locationPool
|
|
*/
|
|
constructor(scriptId, rawLocation, binding, updateDelegate, locationPool) {
|
|
super(updateDelegate, locationPool);
|
|
this._scriptId = scriptId;
|
|
this._rawLocation = rawLocation;
|
|
this._binding = binding;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {!Promise<?Workspace.UISourceCode.UILocation>}
|
|
*/
|
|
async uiLocation() {
|
|
const debuggerModelLocation = this._rawLocation;
|
|
return this._binding.rawLocationToUILocation(debuggerModelLocation);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
dispose() {
|
|
super.dispose();
|
|
this._binding._removeLiveLocation(this);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {!Promise<boolean>}
|
|
*/
|
|
async isIgnoreListed() {
|
|
const uiLocation = await this.uiLocation();
|
|
return uiLocation ? IgnoreListManager.instance().isIgnoreListedUISourceCode(uiLocation.uiSourceCode) : false;
|
|
}
|
|
}
|
|
|
|
class StackTraceTopFrameLocation extends LiveLocationWithPool {
|
|
/**
|
|
* @param {function(!LiveLocation): !Promise<?>} updateDelegate
|
|
* @param {!LiveLocationPool} locationPool
|
|
*/
|
|
constructor(updateDelegate, locationPool) {
|
|
super(updateDelegate, locationPool);
|
|
this._updateScheduled = true;
|
|
/** @type {?LiveLocation} */
|
|
this._current = null;
|
|
/** @type {?Array<!LiveLocation>} */
|
|
this._locations = null;
|
|
}
|
|
|
|
/**
|
|
* @param {!Array<!SDK.DebuggerModel.Location>} rawLocations
|
|
* @param {!DebuggerWorkspaceBinding} binding
|
|
* @param {function(!LiveLocation): !Promise<?>} updateDelegate
|
|
* @param {!LiveLocationPool} locationPool
|
|
* @return {!Promise<!StackTraceTopFrameLocation>}
|
|
*/
|
|
static async createStackTraceTopFrameLocation(rawLocations, binding, updateDelegate, locationPool) {
|
|
const location = new StackTraceTopFrameLocation(updateDelegate, locationPool);
|
|
const locationsPromises = rawLocations.map(
|
|
rawLocation => binding.createLiveLocation(rawLocation, location._scheduleUpdate.bind(location), locationPool));
|
|
location._locations =
|
|
/** @type {!Array<!Location>} */ ((await Promise.all(locationsPromises)).filter(l => Boolean(l)));
|
|
await location._updateLocation();
|
|
return location;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {!Promise<?Workspace.UISourceCode.UILocation>}
|
|
*/
|
|
async uiLocation() {
|
|
return this._current ? this._current.uiLocation() : null;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {!Promise<boolean>}
|
|
*/
|
|
async isIgnoreListed() {
|
|
return this._current ? this._current.isIgnoreListed() : false;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
dispose() {
|
|
super.dispose();
|
|
if (this._locations) {
|
|
for (const location of this._locations) {
|
|
location.dispose();
|
|
}
|
|
}
|
|
this._locations = null;
|
|
this._current = null;
|
|
}
|
|
|
|
async _scheduleUpdate() {
|
|
if (this._updateScheduled) {
|
|
return;
|
|
}
|
|
this._updateScheduled = true;
|
|
queueMicrotask(() => {
|
|
this._updateLocation();
|
|
});
|
|
}
|
|
|
|
async _updateLocation() {
|
|
this._updateScheduled = false;
|
|
if (!this._locations || this._locations.length === 0) {
|
|
return;
|
|
}
|
|
|
|
this._current = this._locations[0];
|
|
for (const location of this._locations) {
|
|
if (!(await location.isIgnoreListed())) {
|
|
this._current = location;
|
|
break;
|
|
}
|
|
}
|
|
this.update();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @interface
|
|
*/
|
|
export class DebuggerSourceMapping {
|
|
/**
|
|
* @param {!SDK.DebuggerModel.Location} rawLocation
|
|
* @return {?Workspace.UISourceCode.UILocation}
|
|
*/
|
|
rawLocationToUILocation(rawLocation) {
|
|
throw new Error('Not yet implemented');
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
|
|
* @param {number} lineNumber
|
|
* @param {number=} columnNumber
|
|
* @return {!Array<!SDK.DebuggerModel.Location>}
|
|
*/
|
|
uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber) {
|
|
throw new Error('Not yet implemented');
|
|
}
|
|
}
|