blob: 27e62065bb59b8550eb8eda6571525b1a463bcca [file] [log] [blame]
// 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.
WebInspector.SourceMapNamesResolver = {};
WebInspector.SourceMapNamesResolver._cachedMapSymbol = Symbol("cache");
WebInspector.SourceMapNamesResolver._cachedPromiseSymbol = Symbol("cachePromise");
/**
* @param {!WebInspector.DebuggerModel.Scope} scope
* @return {!Promise.<!Map<string, string>>}
*/
WebInspector.SourceMapNamesResolver._resolveScope = function(scope)
{
var cachedMap = scope[WebInspector.SourceMapNamesResolver._cachedMapSymbol];
if (cachedMap)
return Promise.resolve(cachedMap);
var cachedPromise = scope[WebInspector.SourceMapNamesResolver._cachedPromiseSymbol];
if (cachedPromise)
return cachedPromise;
var startLocation = scope.startLocation();
var endLocation = scope.endLocation();
if (scope.type() === DebuggerAgent.ScopeType.Global || !startLocation || !endLocation || !startLocation.script().sourceMapURL || (startLocation.script() !== endLocation.script()))
return Promise.resolve(new Map());
var script = startLocation.script();
var sourceMap = WebInspector.debuggerWorkspaceBinding.sourceMapForScript(script);
if (!sourceMap)
return Promise.resolve(new Map());
var promise = script.requestContent().then(onContent);
scope[WebInspector.SourceMapNamesResolver._cachedPromiseSymbol] = promise;
return promise;
/**
* @param {?string} content
* @return {!Map<string, string>}
*/
function onContent(content)
{
if (!content)
return new Map();
var startLocation = scope.startLocation();
var endLocation = scope.endLocation();
var textRange = new WebInspector.TextRange(startLocation.lineNumber, startLocation.columnNumber, endLocation.lineNumber, endLocation.columnNumber);
var scopeText = textRange.extract(content);
var scopeStart = textRange.toSourceRange(content).offset;
var prefix = "function fui";
var root = acorn.parse(prefix + scopeText, {});
/** @type {!Array<!ESTree.Node>} */
var identifiers = [];
var functionDeclarationCounter = 0;
var walker = new WebInspector.ESTreeWalker(beforeVisit, afterVisit);
/**
* @param {!ESTree.Node} node
* @return {boolean}
*/
function isFunction(node)
{
return node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
}
/**
* @param {!ESTree.Node} node
*/
function beforeVisit(node)
{
if (isFunction(node))
functionDeclarationCounter++;
if (functionDeclarationCounter > 1)
return;
if (isFunction(node) && node.params)
identifiers.pushAll(node.params);
if (node.type === "VariableDeclarator")
identifiers.push(/** @type {!ESTree.Node} */(node.id));
}
/**
* @param {!ESTree.Node} node
*/
function afterVisit(node)
{
if (isFunction(node))
functionDeclarationCounter--;
}
walker.walk(root);
var namesMapping = new Map();
var lineEndings = content.lineEndings();
for (var i = 0; i < identifiers.length; ++i) {
var id = identifiers[i];
var start = scopeStart + id.start - prefix.length;
var lineNumber = lineEndings.lowerBound(start);
var columnNumber = start - (lineNumber === 0 ? 0 : (lineEndings[lineNumber - 1] + 1));
var entry = sourceMap.findEntry(lineNumber, columnNumber);
if (entry)
namesMapping.set(id.name, entry.name);
}
scope[WebInspector.SourceMapNamesResolver._cachedMapSymbol] = namesMapping;
delete scope[WebInspector.SourceMapNamesResolver._cachedPromiseSymbol];
return namesMapping;
}
}
/**
* @param {!WebInspector.DebuggerModel.CallFrame} callFrame
* @return {!Promise.<!Map<string, string>>}
*/
WebInspector.SourceMapNamesResolver._allVariablesInCallFrame = function(callFrame)
{
var cached = callFrame[WebInspector.SourceMapNamesResolver._cachedMapSymbol];
if (cached)
return Promise.resolve(cached);
var promises = [];
var scopeChain = callFrame.scopeChain();
for (var i = 0; i < scopeChain.length; ++i)
promises.push(WebInspector.SourceMapNamesResolver._resolveScope(scopeChain[i]));
return Promise.all(promises).then(mergeVariables);
/**
* @param {!Array<!Map<string, string>>} nameMappings
* @return {!Map<string, string>}
*/
function mergeVariables(nameMappings)
{
var reverseMapping = new Map();
for (var map of nameMappings) {
for (var compiledName of map.keys()) {
var originalName = map.get(compiledName);
if (!reverseMapping.has(originalName))
reverseMapping.set(originalName, compiledName);
}
}
callFrame[WebInspector.SourceMapNamesResolver._cachedMapSymbol] = reverseMapping;
return reverseMapping;
}
}
/**
* @param {!WebInspector.DebuggerModel.CallFrame} callFrame
* @param {string} originalText
* @param {!WebInspector.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @param {number} startColumnNumber
* @param {number} endColumnNumber
* @return {!Promise<string>}
*/
WebInspector.SourceMapNamesResolver.resolveExpression = function(callFrame, originalText, uiSourceCode, lineNumber, startColumnNumber, endColumnNumber)
{
if (!Runtime.experiments.isEnabled("resolveVariableNames"))
return Promise.resolve("");
return WebInspector.SourceMapNamesResolver._allVariablesInCallFrame(callFrame).then(findCompiledName);
/**
* @param {!Map<string, string>} reverseMapping
* @return {!Promise<string>}
*/
function findCompiledName(reverseMapping)
{
if (reverseMapping.has(originalText))
return Promise.resolve(reverseMapping.get(originalText) || "");
return WebInspector.SourceMapNamesResolver._resolveExpression(callFrame, uiSourceCode, lineNumber, startColumnNumber, endColumnNumber)
}
}
/**
* @param {!WebInspector.DebuggerModel.CallFrame} callFrame
* @param {!WebInspector.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @param {number} startColumnNumber
* @param {number} endColumnNumber
* @return {!Promise<string>}
*/
WebInspector.SourceMapNamesResolver._resolveExpression = function(callFrame, uiSourceCode, lineNumber, startColumnNumber, endColumnNumber)
{
var target = callFrame.target();
var rawLocation = WebInspector.debuggerWorkspaceBinding.uiLocationToRawLocation(target, uiSourceCode, lineNumber, startColumnNumber);
if (!rawLocation)
return Promise.resolve("");
var script = rawLocation.script();
var sourceMap = WebInspector.debuggerWorkspaceBinding.sourceMapForScript(script);
if (!sourceMap)
return Promise.resolve("");
return script.requestContent().then(onContent);
/**
* @param {?string} content
* @return {string}
*/
function onContent(content)
{
if (!content)
return "";
var textRange = sourceMap.reverseMapTextRange(uiSourceCode.url(), new WebInspector.TextRange(lineNumber, startColumnNumber, lineNumber, endColumnNumber));
var originalText = textRange.extract(content);
if (!originalText)
return "";
var tokenizer = acorn.tokenizer(originalText, {ecmaVersion: 6});
try {
var token = tokenizer.getToken();
while (token.type !== acorn.tokTypes.eof && WebInspector.AcornTokenizer.punctuator(token))
token = tokenizer.getToken();
var startIndex = token.start;
var endIndex = token.end;
var openBracketsCounter = 0;
while (token.type !== acorn.tokTypes.eof) {
var isIdentifier = WebInspector.AcornTokenizer.identifier(token);
var isThis = WebInspector.AcornTokenizer.keyword(token, "this");
var isString = token.type === acorn.tokTypes.string;
if (!isThis && !isIdentifier && !isString)
break;
endIndex = token.end;
token = tokenizer.getToken();
while (WebInspector.AcornTokenizer.punctuator(token, ".[]")) {
if (WebInspector.AcornTokenizer.punctuator(token, "["))
openBracketsCounter++;
if (WebInspector.AcornTokenizer.punctuator(token, "]")) {
endIndex = openBracketsCounter > 0 ? token.end : endIndex;
openBracketsCounter--;
}
token = tokenizer.getToken();
}
}
return originalText.substring(startIndex, endIndex);
} catch (e) {
return "";
}
}
}
/**
* @param {!WebInspector.DebuggerModel.Scope} scope
* @return {!WebInspector.RemoteObject}
*/
WebInspector.SourceMapNamesResolver.resolveScopeInObject = function(scope)
{
if (!Runtime.experiments.isEnabled("resolveVariableNames"))
return scope.object();
var startLocation = scope.startLocation();
var endLocation = scope.endLocation();
if (scope.type() === DebuggerAgent.ScopeType.Global || !startLocation || !endLocation || !startLocation.script().sourceMapURL || startLocation.script() !== endLocation.script())
return scope.object();
return new WebInspector.SourceMapNamesResolver.RemoteObject(scope);
}
/**
* @constructor
* @extends {WebInspector.RemoteObject}
* @param {!WebInspector.DebuggerModel.Scope} scope
*/
WebInspector.SourceMapNamesResolver.RemoteObject = function(scope)
{
WebInspector.RemoteObject.call(this);
this._scope = scope;
this._object = scope.object();
};
WebInspector.SourceMapNamesResolver.RemoteObject.prototype = {
/**
* @override
* @return {?RuntimeAgent.CustomPreview}
*/
customPreview: function()
{
return this._object.customPreview();
},
/**
* @override
* @return {string}
*/
get type()
{
return this._object.type;
},
/**
* @override
* @return {string|undefined}
*/
get subtype()
{
return this._object.subtype;
},
/**
* @override
* @return {string|undefined}
*/
get description()
{
return this._object.description;
},
/**
* @override
* @return {boolean}
*/
get hasChildren()
{
return this._object.hasChildren;
},
/**
* @override
* @return {number}
*/
arrayLength: function()
{
return this._object.arrayLength();
},
/**
* @override
* @param {function(?Array.<!WebInspector.RemoteObjectProperty>, ?Array.<!WebInspector.RemoteObjectProperty>)} callback
*/
getOwnProperties: function(callback)
{
this._object.getOwnProperties(callback);
},
/**
* @override
* @param {boolean} accessorPropertiesOnly
* @param {function(?Array<!WebInspector.RemoteObjectProperty>, ?Array<!WebInspector.RemoteObjectProperty>)} callback
*/
getAllProperties: function(accessorPropertiesOnly, callback)
{
/**
* @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
* @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
* @this {WebInspector.SourceMapNamesResolver.RemoteObject}
*/
function wrappedCallback(properties, internalProperties)
{
WebInspector.SourceMapNamesResolver._resolveScope(this._scope).then(resolveNames.bind(null, properties, internalProperties))
}
/**
* @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
* @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
* @param {!Map<string, string>} namesMapping
*/
function resolveNames(properties, internalProperties, namesMapping)
{
var newProperties = [];
if (properties) {
for (var i = 0; i < properties.length; ++i) {
var property = properties[i];
var name = namesMapping.get(property.name) || properties[i].name;
newProperties.push(new WebInspector.RemoteObjectProperty(name, property.value, property.enumerable, property.writable, property.isOwn, property.wasThrown, property.symbol, property.synthetic));
}
}
callback(newProperties, internalProperties);
}
this._object.getAllProperties(accessorPropertiesOnly, wrappedCallback.bind(this));
},
/**
* @override
* @param {string|!RuntimeAgent.CallArgument} argumentName
* @param {string} value
* @param {function(string=)} callback
*/
setPropertyValue: function(argumentName, value, callback)
{
WebInspector.SourceMapNamesResolver._resolveScope(this._scope).then(resolveName.bind(this));
/**
* @param {!Map<string, string>} namesMapping
* @this {WebInspector.SourceMapNamesResolver.RemoteObject}
*/
function resolveName(namesMapping)
{
var name;
if (typeof argumentName === "string")
name = argumentName;
else
name = /** @type {string} */ (argumentName.value);
var actualName = name;
for (var compiledName of namesMapping.keys()) {
if (namesMapping.get(compiledName) === name) {
actualName = compiledName;
break;
}
}
this._object.setPropertyValue(actualName, value, callback);
}
},
/**
* @override
* @return {!Promise<?Array<!WebInspector.EventListener>>}
*/
eventListeners: function()
{
return this._object.eventListeners();
},
/**
* @override
* @param {!RuntimeAgent.CallArgument} name
* @param {function(string=)} callback
*/
deleteProperty: function(name, callback)
{
this._object.deleteProperty(name, callback);
},
/**
* @override
* @param {function(this:Object, ...)} functionDeclaration
* @param {!Array<!RuntimeAgent.CallArgument>=} args
* @param {function(?WebInspector.RemoteObject, boolean=)=} callback
*/
callFunction: function(functionDeclaration, args, callback)
{
this._object.callFunction(functionDeclaration, args, callback);
},
/**
* @override
* @param {function(this:Object, ...)} functionDeclaration
* @param {!Array<!RuntimeAgent.CallArgument>|undefined} args
* @param {function(*)} callback
*/
callFunctionJSON: function(functionDeclaration, args, callback)
{
this._object.callFunctionJSON(functionDeclaration, args, callback);
},
/**
* @override
* @return {!WebInspector.Target}
*/
target: function()
{
return this._object.target();
},
/**
* @override
* @return {?WebInspector.DebuggerModel}
*/
debuggerModel: function()
{
return this._object.debuggerModel();
},
/**
* @override
* @return {boolean}
*/
isNode: function()
{
return this._object.isNode();
},
/**
* @override
* @param {function(?WebInspector.DebuggerModel.FunctionDetails)} callback
*/
functionDetails: function(callback)
{
this._object.functionDetails(callback);
},
/**
* @override
* @param {function(?WebInspector.DebuggerModel.GeneratorObjectDetails)} callback
*/
generatorObjectDetails: function(callback)
{
this._object.generatorObjectDetails(callback);
},
/**
* @override
* @param {function(?Array<!DebuggerAgent.CollectionEntry>)} callback
*/
collectionEntries: function(callback)
{
this._object.collectionEntries(callback);
},
__proto__: WebInspector.RemoteObject.prototype
}