Chromium Code Reviews| Index: third_party/WebKit/Source/devtools/front_end/bindings/SourceMapNamesResolver.js |
| diff --git a/third_party/WebKit/Source/devtools/front_end/bindings/SourceMapNamesResolver.js b/third_party/WebKit/Source/devtools/front_end/bindings/SourceMapNamesResolver.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..eb820e31d52ae042bf332256bf2f62658d174af9 |
| --- /dev/null |
| +++ b/third_party/WebKit/Source/devtools/front_end/bindings/SourceMapNamesResolver.js |
| @@ -0,0 +1,375 @@ |
| +// 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. |
| + |
| +/** |
| + * @constructor |
| + */ |
| +WebInspector.SourceMapNamesResolver = function() |
| +{ |
| + this._resolvingPromises = new Map(); |
| +} |
| + |
| +WebInspector.SourceMapNamesResolver._cacheSymbol = Symbol("cache"); |
| + |
| +WebInspector.SourceMapNamesResolver.prototype = { |
| + /** |
| + * @param {!WebInspector.DebuggerModel.Scope} scope |
| + * @return {!Promise.<!Map<string, string>>} |
| + */ |
| + _resolveScope: function(scope) |
| + { |
| + var cachedMap = scope[WebInspector.SourceMapNamesResolver._cacheSymbol]; |
| + if (cachedMap) |
| + return Promise.resolve(cachedMap); |
| + |
| + var startLocation = scope.startLocation(); |
| + var endLocation = scope.endLocation(); |
| + var script = startLocation.script(); |
| + |
| + if (scope.type() === DebuggerAgent.ScopeType.Global || !startLocation || !endLocation || !script.sourceMapURL || script !== endLocation.script()) |
| + return Promise.resolve(new Map()); |
| + |
| + var cachedPromise = this._resolvingPromises.get(scope); |
|
dgozman
2016/02/22 17:24:56
Move this check before |var startLocation = ...|
sergeyv
2016/02/22 21:42:54
Done.
|
| + if (cachedPromise) |
| + return cachedPromise; |
| + |
| + var sourceMap = WebInspector.debuggerWorkspaceBinding.sourceMapForScript(script); |
| + if (!sourceMap) |
| + return Promise.resolve(new Map()); |
| + |
| + var promise = script.requestContent().then(onContent.bind(this)); |
| + this._resolvingPromises.set(scope, promise); |
| + return promise; |
| + |
| + /** |
| + * @param {?string} content |
| + * @return {!Map<string, string>} |
| + * @this {WebInspector.SourceMapNamesResolver} |
| + */ |
| + 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"; |
|
dgozman
2016/02/22 17:24:56
Maybe add () after function name for perfectly val
sergeyv
2016/02/22 21:42:54
usually scopes start with (); so we shouldn't add
|
| + var root = acorn.parse(prefix + scopeText, {}); |
| + var declarators = []; |
| + var functionDeclarationCounter = 0; |
| + var walker = new WebInspector.ESTreeWalker(beforeVisit, afterVisit); |
| + |
| + /** |
| + * @param {!ESTree.Node} node |
| + */ |
| + function beforeVisit(node) |
| + { |
| + if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") |
|
dgozman
2016/02/22 17:24:56
Does it support arrow functions?
sergeyv
2016/02/22 21:42:54
I will check this && support it in follow-ups
|
| + functionDeclarationCounter++; |
| + |
| + if (node.type === "VariableDeclarator" && functionDeclarationCounter === 1) |
| + declarators.push(node) |
|
dgozman
2016/02/22 17:24:56
missing semicolon
sergeyv
2016/02/22 21:42:54
Done.
|
| + } |
| + |
| + /** |
| + * @param {!ESTree.Node} node |
| + */ |
| + function afterVisit(node) |
| + { |
| + if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") |
| + functionDeclarationCounter--; |
| + } |
| + |
| + walker.walk(root); |
| + |
| + var namesMapping = new Map(); |
| + |
| + for (var i = 0; i < declarators.length; ++i) { |
| + var id = declarators[i].id; |
| + var start = scopeStart + id.start - prefix.length; |
| + |
| + var lineEndings = content.lineEndings(); |
|
dgozman
2016/02/22 17:24:56
Move this call out of the loop.
sergeyv
2016/02/22 21:42:54
Done. But there was no real necessity in this: lin
|
| + 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); |
| + } |
| + |
| + this._resolvingPromises.remove(scope); |
| + scope[WebInspector.SourceMapNamesResolver._cacheSymbol] = namesMapping; |
| + return namesMapping; |
| + } |
| + }, |
| + |
| + /** |
| + * @param {!WebInspector.DebuggerModel.Scope} scope |
| + * @return {!WebInspector.RemoteObject} |
| + */ |
| + 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()) { |
|
dgozman
2016/02/22 17:24:56
Is this operation common enough to justify reverse
sergeyv
2016/02/22 21:42:54
It is used only when user changes a property value
|
| + 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 |
| +} |
| + |
| +WebInspector.sourceMapNamesResolver = new WebInspector.SourceMapNamesResolver(); |