debug-server-next
Version:
Dev server for hippy-core.
338 lines (337 loc) • 14 kB
JavaScript
// Copyright 2016 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.
/* eslint-disable rulesdir/no_underscored_properties */
import * as Platform from '../../core/platform/platform.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Bindings from '../../models/bindings/bindings.js';
import * as Formatter from '../../models/formatter/formatter.js';
import * as TextUtils from '../../models/text_utils/text_utils.js';
const scopeToCachedIdentifiersMap = new WeakMap();
const cachedMapByCallFrame = new WeakMap();
export class Identifier {
name;
lineNumber;
columnNumber;
constructor(name, lineNumber, columnNumber) {
this.name = name;
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
}
}
export const scopeIdentifiers = async function (scope) {
if (scope.type() === "global" /* Global */) {
return [];
}
const startLocation = scope.startLocation();
const endLocation = scope.endLocation();
if (!startLocation || !endLocation) {
return [];
}
const script = startLocation.script();
if (!script || !script.sourceMapURL || script !== endLocation.script()) {
return [];
}
const { content } = await script.requestContent();
if (!content) {
return [];
}
const text = new TextUtils.Text.Text(content);
const scopeRange = new TextUtils.TextRange.TextRange(startLocation.lineNumber, startLocation.columnNumber, endLocation.lineNumber, endLocation.columnNumber);
const scopeText = text.extract(scopeRange);
const scopeStart = text.toSourceRange(scopeRange).offset;
const prefix = 'function fui';
const identifiers = await Formatter.FormatterWorkerPool.formatterWorkerPool().javaScriptIdentifiers(prefix + scopeText);
const result = [];
const cursor = new TextUtils.TextCursor.TextCursor(text.lineEndings());
for (const id of identifiers) {
if (id.offset < prefix.length) {
continue;
}
const start = scopeStart + id.offset - prefix.length;
cursor.resetTo(start);
result.push(new Identifier(id.name, cursor.lineNumber(), cursor.columnNumber()));
}
return result;
};
export const resolveScopeChain = async function (callFrame) {
if (!callFrame) {
return null;
}
const { pluginManager } = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
if (pluginManager) {
const scopeChain = await pluginManager.resolveScopeChain(callFrame);
if (scopeChain) {
return scopeChain;
}
}
return callFrame.scopeChain();
};
export const resolveScope = async (scope) => {
let identifiersPromise = scopeToCachedIdentifiersMap.get(scope);
if (!identifiersPromise) {
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
identifiersPromise = (async () => {
const namesMapping = new Map();
const script = scope.callFrame().script;
const sourceMap = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().sourceMapForScript(script);
if (sourceMap) {
const textCache = new Map();
// Extract as much as possible from SourceMap and resolve
// missing identifier names from SourceMap ranges.
const promises = [];
for (const id of await scopeIdentifiers(scope)) {
const entry = sourceMap.findEntry(id.lineNumber, id.columnNumber);
if (entry && entry.name) {
namesMapping.set(id.name, entry.name);
}
else {
promises.push(resolveSourceName(script, sourceMap, id, textCache).then(sourceName => {
if (sourceName) {
namesMapping.set(id.name, sourceName);
}
}));
}
}
await Promise.all(promises).then(getScopeResolvedForTest());
}
return namesMapping;
})();
scopeToCachedIdentifiersMap.set(scope, identifiersPromise);
}
return await identifiersPromise;
async function resolveSourceName(script, sourceMap, id, textCache) {
const startEntry = sourceMap.findEntry(id.lineNumber, id.columnNumber);
const endEntry = sourceMap.findEntry(id.lineNumber, id.columnNumber + id.name.length);
if (!startEntry || !endEntry || !startEntry.sourceURL || startEntry.sourceURL !== endEntry.sourceURL ||
!startEntry.sourceLineNumber || !startEntry.sourceColumnNumber || !endEntry.sourceLineNumber ||
!endEntry.sourceColumnNumber) {
return null;
}
const sourceTextRange = new TextUtils.TextRange.TextRange(startEntry.sourceLineNumber, startEntry.sourceColumnNumber, endEntry.sourceLineNumber, endEntry.sourceColumnNumber);
const uiSourceCode = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().uiSourceCodeForSourceMapSourceURL(script.debuggerModel, startEntry.sourceURL, script.isContentScript());
if (!uiSourceCode) {
return null;
}
const { content } = await uiSourceCode.requestContent();
if (!content) {
return null;
}
let text = textCache.get(content);
if (!text) {
text = new TextUtils.Text.Text(content);
textCache.set(content, text);
}
const originalIdentifier = text.extract(sourceTextRange).trim();
return /[a-zA-Z0-9_$]+/.test(originalIdentifier) ? originalIdentifier : null;
}
};
export const allVariablesInCallFrame = async (callFrame) => {
const cachedMap = cachedMapByCallFrame.get(callFrame);
if (cachedMap) {
return cachedMap;
}
const scopeChain = callFrame.scopeChain();
const nameMappings = await Promise.all(scopeChain.map(resolveScope));
const reverseMapping = new Map();
for (const map of nameMappings) {
for (const [compiledName, originalName] of map) {
if (originalName && !reverseMapping.has(originalName)) {
reverseMapping.set(originalName, compiledName);
}
}
}
cachedMapByCallFrame.set(callFrame, reverseMapping);
return reverseMapping;
};
export const resolveExpression = async (callFrame, originalText, uiSourceCode, lineNumber, startColumnNumber, endColumnNumber) => {
if (uiSourceCode.mimeType() === 'application/wasm') {
// For WebAssembly disassembly, lookup the different possiblities.
return `memories["${originalText}"] ?? locals["${originalText}"] ?? tables["${originalText}"] ?? functions["${originalText}"] ?? globals["${originalText}"]`;
}
if (!uiSourceCode.contentType().isFromSourceMap()) {
return '';
}
const reverseMapping = await allVariablesInCallFrame(callFrame);
if (reverseMapping.has(originalText)) {
return reverseMapping.get(originalText);
}
const rawLocations = await Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().uiLocationToRawLocations(uiSourceCode, lineNumber, startColumnNumber);
const rawLocation = rawLocations.find(location => location.debuggerModel === callFrame.debuggerModel);
if (!rawLocation) {
return '';
}
const script = rawLocation.script();
if (!script) {
return '';
}
const sourceMap = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().sourceMapForScript(script);
if (!sourceMap) {
return '';
}
const { content } = await script.requestContent();
if (!content) {
return '';
}
const text = new TextUtils.Text.Text(content);
const textRange = sourceMap.reverseMapTextRange(uiSourceCode.url(), new TextUtils.TextRange.TextRange(lineNumber, startColumnNumber, lineNumber, endColumnNumber));
if (!textRange) {
return '';
}
const subjectText = text.extract(textRange);
if (!subjectText) {
return '';
}
return await Formatter.FormatterWorkerPool.formatterWorkerPool().evaluatableJavaScriptSubstring(subjectText);
};
export const resolveThisObject = async (callFrame) => {
if (!callFrame) {
return null;
}
const scopeChain = callFrame.scopeChain();
if (scopeChain.length === 0) {
return callFrame.thisObject();
}
const namesMapping = await resolveScope(scopeChain[0]);
const thisMappings = Platform.MapUtilities.inverse(namesMapping).get('this');
if (!thisMappings || thisMappings.size !== 1) {
return callFrame.thisObject();
}
const [expression] = thisMappings.values();
const result = await callFrame.evaluate({
expression,
objectGroup: 'backtrace',
includeCommandLineAPI: false,
silent: true,
returnByValue: false,
generatePreview: true,
});
if ('exceptionDetails' in result) {
return !result.exceptionDetails && result.object ? result.object : callFrame.thisObject();
}
return null;
};
export const resolveScopeInObject = function (scope) {
const startLocation = scope.startLocation();
const endLocation = scope.endLocation();
const startLocationScript = startLocation ? startLocation.script() : null;
if (scope.type() === "global" /* Global */ || !startLocationScript || !endLocation ||
!startLocationScript.sourceMapURL || startLocationScript !== endLocation.script()) {
return scope.object();
}
return new RemoteObject(scope);
};
export class RemoteObject extends SDK.RemoteObject.RemoteObject {
_scope;
_object;
constructor(scope) {
super();
this._scope = scope;
this._object = scope.object();
}
customPreview() {
return this._object.customPreview();
}
get objectId() {
return this._object.objectId;
}
get type() {
return this._object.type;
}
get subtype() {
return this._object.subtype;
}
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get value() {
return this._object.value;
}
get description() {
return this._object.description;
}
get hasChildren() {
return this._object.hasChildren;
}
get preview() {
return this._object.preview;
}
arrayLength() {
return this._object.arrayLength();
}
getOwnProperties(generatePreview) {
return this._object.getOwnProperties(generatePreview);
}
async getAllProperties(accessorPropertiesOnly, generatePreview) {
const allProperties = await this._object.getAllProperties(accessorPropertiesOnly, generatePreview);
const namesMapping = await resolveScope(this._scope);
const properties = allProperties.properties;
const internalProperties = allProperties.internalProperties;
const newProperties = [];
if (properties) {
for (let i = 0; i < properties.length; ++i) {
const property = properties[i];
const name = namesMapping.get(property.name) || properties[i].name;
if (!property.value) {
continue;
}
newProperties.push(new SDK.RemoteObject.RemoteObjectProperty(name, property.value, property.enumerable, property.writable, property.isOwn, property.wasThrown, property.symbol, property.synthetic));
}
}
return { properties: newProperties, internalProperties: internalProperties };
}
async setPropertyValue(argumentName, value) {
const namesMapping = await resolveScope(this._scope);
let name;
if (typeof argumentName === 'string') {
name = argumentName;
}
else {
name = argumentName.value;
}
let actualName = name;
for (const compiledName of namesMapping.keys()) {
if (namesMapping.get(compiledName) === name) {
actualName = compiledName;
break;
}
}
return this._object.setPropertyValue(actualName, value);
}
async deleteProperty(name) {
return this._object.deleteProperty(name);
}
callFunction(functionDeclaration, args) {
return this._object.callFunction(functionDeclaration, args);
}
callFunctionJSON(functionDeclaration, args) {
return this._object.callFunctionJSON(functionDeclaration, args);
}
release() {
this._object.release();
}
debuggerModel() {
return this._object.debuggerModel();
}
runtimeModel() {
return this._object.runtimeModel();
}
isNode() {
return this._object.isNode();
}
}
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any
let _scopeResolvedForTest = function () { };
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any
export const getScopeResolvedForTest = () => {
return _scopeResolvedForTest;
};
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any
export const setScopeResolvedForTest = (scope) => {
_scopeResolvedForTest = scope;
};