Files
react-native-devtools-frontend/front_end/bindings/DebuggerWorkspaceBinding.js
T
Tim van der Lippe e4d64e7ae0 Remove unrestricted from comments that include other annotations
For all comments that contained both the unrestricted annotation
and any other potential annotation, remove only the unrestricted
annotation.

R=szuend@chromium.org

No-Presubmit: True
Bug: 1011811
Change-Id: I27dd67f657cdb1a9233d768eea81cabe2591f5f7
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2569759
Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org>
Reviewed-by: Simon Zünd <szuend@chromium.org>
2020-12-02 15:31:52 +00:00

710 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 {BlackboxManager} from './BlackboxManager.js';
import {CompilerScriptMapping} from './CompilerScriptMapping.js';
import {DebuggerLanguagePluginManager} from './DebuggerLanguagePlugins.js';
import {DefaultScriptMapping} from './DefaultScriptMapping.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 settings: 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>}
*/
createLiveLocation(rawLocation, updateDelegate, locationPool) {
const script = rawLocation.script();
if (!script) {
return Promise.resolve(null);
}
const modelData = this._debuggerModelToData.get(script.debuggerModel);
if (!modelData) {
return Promise.resolve(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 {!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._script.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.Multimap<!SDK.Script.Script, !Location>} */
this._locations = new Platform.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.script() !== null);
const script = /** @type {!SDK.Script.Script} */ (rawLocation.script());
const location = new Location(script, rawLocation, this._debuggerWorkspaceBinding, updateDelegate, locationPool);
this._locations.set(script, location);
await location.update();
return location;
}
/**
* @param {!Location} location
*/
_disposeLocation(location) {
this._locations.delete(location._script, location);
}
/**
* @param {!SDK.Script.Script} script
*/
async _updateLocations(script) {
const promises = [];
for (const location of this._locations.get(script)) {
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 !!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 {!SDK.Script.Script} script
* @param {!SDK.DebuggerModel.Location} rawLocation
* @param {!DebuggerWorkspaceBinding} binding
* @param {function(!LiveLocation): !Promise<?>} updateDelegate
* @param {!LiveLocationPool} locationPool
*/
constructor(script, rawLocation, binding, updateDelegate, locationPool) {
super(updateDelegate, locationPool);
this._script = script;
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 isBlackboxed() {
const uiLocation = await this.uiLocation();
return uiLocation ? BlackboxManager.instance().isBlackboxedUISourceCode(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 => !!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 isBlackboxed() {
return this._current ? this._current.isBlackboxed() : 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.isBlackboxed())) {
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');
}
}