| // 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. | |
| Sources.SourceMapNamesResolver = {}; | |
| Sources.SourceMapNamesResolver._cachedMapSymbol = Symbol('cache'); | |
| Sources.SourceMapNamesResolver._cachedIdentifiersSymbol = Symbol('cachedIdentifiers'); | |
| /** | |
| * @unrestricted | |
| */ | |
| Sources.SourceMapNamesResolver.Identifier = class { | |
| /** | |
| * @param {string} name | |
| * @param {number} lineNumber | |
| * @param {number} columnNumber | |
| */ | |
| constructor(name, lineNumber, columnNumber) { | |
| this.name = name; | |
| this.lineNumber = lineNumber; | |
| this.columnNumber = columnNumber; | |
| } | |
| }; | |
| /** | |
| * @param {!SDK.DebuggerModel.Scope} scope | |
| * @return {!Promise<!Array<!Sources.SourceMapNamesResolver.Identifier>>} | |
| */ | |
| Sources.SourceMapNamesResolver._scopeIdentifiers = function(scope) { | |
| const startLocation = scope.startLocation(); | |
| const endLocation = scope.endLocation(); | |
| if (scope.type() === Protocol.Debugger.ScopeType.Global || !startLocation || !endLocation || | |
| !startLocation.script() || !startLocation.script().sourceMapURL || | |
| (startLocation.script() !== endLocation.script())) { | |
| return Promise.resolve(/** @type {!Array<!Sources.SourceMapNamesResolver.Identifier>}*/ ([])); | |
| } | |
| const script = startLocation.script(); | |
| return script.requestContent().then(onContent); | |
| /** | |
| * @param {!Common.DeferredContent} deferredContent | |
| * @return {!Promise<!Array<!Sources.SourceMapNamesResolver.Identifier>>} | |
| */ | |
| function onContent(deferredContent) { | |
| if (!deferredContent.content) { | |
| return Promise.resolve(/** @type {!Array<!Sources.SourceMapNamesResolver.Identifier>}*/ ([])); | |
| } | |
| const content = deferredContent.content; | |
| const text = new TextUtils.Text(content); | |
| const scopeRange = new TextUtils.TextRange( | |
| startLocation.lineNumber, startLocation.columnNumber, endLocation.lineNumber, endLocation.columnNumber); | |
| const scopeText = text.extract(scopeRange); | |
| const scopeStart = text.toSourceRange(scopeRange).offset; | |
| const prefix = 'function fui'; | |
| return Formatter.formatterWorkerPool() | |
| .javaScriptIdentifiers(prefix + scopeText) | |
| .then(onIdentifiers.bind(null, text, scopeStart, prefix)); | |
| } | |
| /** | |
| * @param {!TextUtils.Text} text | |
| * @param {number} scopeStart | |
| * @param {string} prefix | |
| * @param {!Array<!{name: string, offset: number}>} identifiers | |
| * @return {!Array<!Sources.SourceMapNamesResolver.Identifier>} | |
| */ | |
| function onIdentifiers(text, scopeStart, prefix, identifiers) { | |
| const result = []; | |
| const cursor = new TextUtils.TextCursor(text.lineEndings()); | |
| for (let i = 0; i < identifiers.length; ++i) { | |
| const id = identifiers[i]; | |
| if (id.offset < prefix.length) { | |
| continue; | |
| } | |
| const start = scopeStart + id.offset - prefix.length; | |
| cursor.resetTo(start); | |
| result.push(new Sources.SourceMapNamesResolver.Identifier(id.name, cursor.lineNumber(), cursor.columnNumber())); | |
| } | |
| return result; | |
| } | |
| }; | |
| /** | |
| * @param {!SDK.DebuggerModel.Scope} scope | |
| * @return {!Promise.<!Map<string, string>>} | |
| */ | |
| Sources.SourceMapNamesResolver._resolveScope = function(scope) { | |
| let identifiersPromise = scope[Sources.SourceMapNamesResolver._cachedIdentifiersSymbol]; | |
| if (identifiersPromise) { | |
| return identifiersPromise; | |
| } | |
| const script = scope.callFrame().script; | |
| const sourceMap = Bindings.debuggerWorkspaceBinding.sourceMapForScript(script); | |
| if (!sourceMap) { | |
| return Promise.resolve(new Map()); | |
| } | |
| /** @type {!Map<string, !TextUtils.Text>} */ | |
| const textCache = new Map(); | |
| identifiersPromise = Sources.SourceMapNamesResolver._scopeIdentifiers(scope).then(onIdentifiers); | |
| scope[Sources.SourceMapNamesResolver._cachedIdentifiersSymbol] = identifiersPromise; | |
| return identifiersPromise; | |
| /** | |
| * @param {!Array<!Sources.SourceMapNamesResolver.Identifier>} identifiers | |
| * @return {!Promise<!Map<string, string>>} | |
| */ | |
| function onIdentifiers(identifiers) { | |
| const namesMapping = new Map(); | |
| // Extract as much as possible from SourceMap. | |
| for (let i = 0; i < identifiers.length; ++i) { | |
| const id = identifiers[i]; | |
| const entry = sourceMap.findEntry(id.lineNumber, id.columnNumber); | |
| if (entry && entry.name) { | |
| namesMapping.set(id.name, entry.name); | |
| } | |
| } | |
| // Resolve missing identifier names from sourcemap ranges. | |
| const promises = []; | |
| for (let i = 0; i < identifiers.length; ++i) { | |
| const id = identifiers[i]; | |
| if (namesMapping.has(id.name)) { | |
| continue; | |
| } | |
| const promise = resolveSourceName(id).then(onSourceNameResolved.bind(null, namesMapping, id)); | |
| promises.push(promise); | |
| } | |
| return Promise.all(promises) | |
| .then(() => Sources.SourceMapNamesResolver._scopeResolvedForTest()) | |
| .then(() => namesMapping); | |
| } | |
| /** | |
| * @param {!Map<string, string>} namesMapping | |
| * @param {!Sources.SourceMapNamesResolver.Identifier} id | |
| * @param {?string} sourceName | |
| */ | |
| function onSourceNameResolved(namesMapping, id, sourceName) { | |
| if (!sourceName) { | |
| return; | |
| } | |
| namesMapping.set(id.name, sourceName); | |
| } | |
| /** | |
| * @param {!Sources.SourceMapNamesResolver.Identifier} id | |
| * @return {!Promise<?string>} | |
| */ | |
| function resolveSourceName(id) { | |
| 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 Promise.resolve(/** @type {?string} */ (null)); | |
| } | |
| const sourceTextRange = new TextUtils.TextRange( | |
| startEntry.sourceLineNumber, startEntry.sourceColumnNumber, endEntry.sourceLineNumber, | |
| endEntry.sourceColumnNumber); | |
| const uiSourceCode = Bindings.debuggerWorkspaceBinding.uiSourceCodeForSourceMapSourceURL( | |
| script.debuggerModel, startEntry.sourceURL, script.isContentScript()); | |
| if (!uiSourceCode) { | |
| return Promise.resolve(/** @type {?string} */ (null)); | |
| } | |
| return uiSourceCode.requestContent().then(deferredContent => { | |
| const content = deferredContent.content; | |
| return onSourceContent(sourceTextRange, content); | |
| }); | |
| } | |
| /** | |
| * @param {!TextUtils.TextRange} sourceTextRange | |
| * @param {?string} content | |
| * @return {?string} | |
| */ | |
| function onSourceContent(sourceTextRange, content) { | |
| if (!content) { | |
| return null; | |
| } | |
| let text = textCache.get(content); | |
| if (!text) { | |
| text = new TextUtils.Text(content); | |
| textCache.set(content, text); | |
| } | |
| const originalIdentifier = text.extract(sourceTextRange).trim(); | |
| return /[a-zA-Z0-9_$]+/.test(originalIdentifier) ? originalIdentifier : null; | |
| } | |
| }; | |
| Sources.SourceMapNamesResolver._scopeResolvedForTest = function() {}; | |
| /** | |
| * @param {!SDK.DebuggerModel.CallFrame} callFrame | |
| * @return {!Promise.<!Map<string, string>>} | |
| */ | |
| Sources.SourceMapNamesResolver._allVariablesInCallFrame = function(callFrame) { | |
| const cached = callFrame[Sources.SourceMapNamesResolver._cachedMapSymbol]; | |
| if (cached) { | |
| return Promise.resolve(cached); | |
| } | |
| const promises = []; | |
| const scopeChain = callFrame.scopeChain(); | |
| for (let i = 0; i < scopeChain.length; ++i) { | |
| promises.push(Sources.SourceMapNamesResolver._resolveScope(scopeChain[i])); | |
| } | |
| return Promise.all(promises).then(mergeVariables); | |
| /** | |
| * @param {!Array<!Map<string, string>>} nameMappings | |
| * @return {!Map<string, string>} | |
| */ | |
| function mergeVariables(nameMappings) { | |
| const reverseMapping = new Map(); | |
| for (const map of nameMappings) { | |
| for (const compiledName of map.keys()) { | |
| const originalName = map.get(compiledName); | |
| if (!reverseMapping.has(originalName)) { | |
| reverseMapping.set(originalName, compiledName); | |
| } | |
| } | |
| } | |
| callFrame[Sources.SourceMapNamesResolver._cachedMapSymbol] = reverseMapping; | |
| return reverseMapping; | |
| } | |
| }; | |
| /** | |
| * @param {!SDK.DebuggerModel.CallFrame} callFrame | |
| * @param {string} originalText | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| * @param {number} lineNumber | |
| * @param {number} startColumnNumber | |
| * @param {number} endColumnNumber | |
| * @return {!Promise<string>} | |
| */ | |
| Sources.SourceMapNamesResolver.resolveExpression = function( | |
| callFrame, originalText, uiSourceCode, lineNumber, startColumnNumber, endColumnNumber) { | |
| if (!uiSourceCode.contentType().isFromSourceMap()) { | |
| return Promise.resolve(''); | |
| } | |
| return Sources.SourceMapNamesResolver._allVariablesInCallFrame(callFrame).then( | |
| reverseMapping => findCompiledName(callFrame.debuggerModel, reverseMapping)); | |
| /** | |
| * @param {!SDK.DebuggerModel} debuggerModel | |
| * @param {!Map<string, string>} reverseMapping | |
| * @return {!Promise<string>} | |
| */ | |
| function findCompiledName(debuggerModel, reverseMapping) { | |
| if (reverseMapping.has(originalText)) { | |
| return Promise.resolve(reverseMapping.get(originalText) || ''); | |
| } | |
| return Sources.SourceMapNamesResolver._resolveExpression( | |
| debuggerModel, uiSourceCode, lineNumber, startColumnNumber, endColumnNumber); | |
| } | |
| }; | |
| /** | |
| * @param {!SDK.DebuggerModel} debuggerModel | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| * @param {number} lineNumber | |
| * @param {number} startColumnNumber | |
| * @param {number} endColumnNumber | |
| * @return {!Promise<string>} | |
| */ | |
| Sources.SourceMapNamesResolver._resolveExpression = function( | |
| debuggerModel, uiSourceCode, lineNumber, startColumnNumber, endColumnNumber) { | |
| const rawLocations = | |
| Bindings.debuggerWorkspaceBinding.uiLocationToRawLocations(uiSourceCode, lineNumber, startColumnNumber); | |
| const rawLocation = rawLocations.find(location => location.debuggerModel === debuggerModel); | |
| if (!rawLocation) { | |
| return Promise.resolve(''); | |
| } | |
| const script = rawLocation.script(); | |
| if (!script) { | |
| return Promise.resolve(''); | |
| } | |
| const sourceMap = /** @type {!SDK.TextSourceMap} */ (Bindings.debuggerWorkspaceBinding.sourceMapForScript(script)); | |
| if (!sourceMap) { | |
| return Promise.resolve(''); | |
| } | |
| return script.requestContent().then(onContent); | |
| /** | |
| * @param {!Common.DeferredContent} deferredContent | |
| * @return {!Promise<string>} | |
| */ | |
| function onContent(deferredContent) { | |
| const content = deferredContent.content; | |
| if (!content) { | |
| return Promise.resolve(''); | |
| } | |
| const text = new TextUtils.Text(content); | |
| const textRange = sourceMap.reverseMapTextRange( | |
| uiSourceCode.url(), new TextUtils.TextRange(lineNumber, startColumnNumber, lineNumber, endColumnNumber)); | |
| const originalText = text.extract(textRange); | |
| if (!originalText) { | |
| return Promise.resolve(''); | |
| } | |
| return Formatter.formatterWorkerPool().evaluatableJavaScriptSubstring(originalText); | |
| } | |
| }; | |
| /** | |
| * @param {?SDK.DebuggerModel.CallFrame} callFrame | |
| * @return {!Promise<?SDK.RemoteObject>} | |
| */ | |
| Sources.SourceMapNamesResolver.resolveThisObject = function(callFrame) { | |
| if (!callFrame) { | |
| return Promise.resolve(/** @type {?SDK.RemoteObject} */ (null)); | |
| } | |
| if (!callFrame.scopeChain().length) { | |
| return Promise.resolve(callFrame.thisObject()); | |
| } | |
| return Sources.SourceMapNamesResolver._resolveScope(callFrame.scopeChain()[0]).then(onScopeResolved); | |
| /** | |
| * @param {!Map<string, string>} namesMapping | |
| * @return {!Promise<?SDK.RemoteObject>} | |
| */ | |
| function onScopeResolved(namesMapping) { | |
| const thisMappings = namesMapping.inverse().get('this'); | |
| if (!thisMappings || thisMappings.size !== 1) { | |
| return Promise.resolve(callFrame.thisObject()); | |
| } | |
| const thisMapping = thisMappings.valuesArray()[0]; | |
| return callFrame | |
| .evaluate({ | |
| expression: thisMapping, | |
| objectGroup: 'backtrace', | |
| includeCommandLineAPI: false, | |
| silent: true, | |
| returnByValue: false, | |
| generatePreview: true | |
| }) | |
| .then(onEvaluated); | |
| } | |
| /** | |
| * @param {!SDK.RuntimeModel.EvaluationResult} result | |
| * @return {?SDK.RemoteObject} | |
| */ | |
| function onEvaluated(result) { | |
| return !result.exceptionDetails && result.object ? result.object : callFrame.thisObject(); | |
| } | |
| }; | |
| /** | |
| * @param {!SDK.DebuggerModel.Scope} scope | |
| * @return {!SDK.RemoteObject} | |
| */ | |
| Sources.SourceMapNamesResolver.resolveScopeInObject = function(scope) { | |
| const startLocation = scope.startLocation(); | |
| const endLocation = scope.endLocation(); | |
| if (scope.type() === Protocol.Debugger.ScopeType.Global || !startLocation || !endLocation || | |
| !startLocation.script() || !startLocation.script().sourceMapURL || | |
| startLocation.script() !== endLocation.script()) { | |
| return scope.object(); | |
| } | |
| return new Sources.SourceMapNamesResolver.RemoteObject(scope); | |
| }; | |
| /** | |
| * @unrestricted | |
| */ | |
| Sources.SourceMapNamesResolver.RemoteObject = class extends SDK.RemoteObject { | |
| /** | |
| * @param {!SDK.DebuggerModel.Scope} scope | |
| */ | |
| constructor(scope) { | |
| super(); | |
| this._scope = scope; | |
| this._object = scope.object(); | |
| } | |
| /** | |
| * @override | |
| * @return {?Protocol.Runtime.CustomPreview} | |
| */ | |
| customPreview() { | |
| return this._object.customPreview(); | |
| } | |
| /** | |
| * @override | |
| * @return {!Protocol.Runtime.RemoteObjectId|undefined} | |
| */ | |
| get objectId() { | |
| return this._object.objectId; | |
| } | |
| /** | |
| * @override | |
| * @return {string} | |
| */ | |
| get type() { | |
| return this._object.type; | |
| } | |
| /** | |
| * @override | |
| * @return {string|undefined} | |
| */ | |
| get subtype() { | |
| return this._object.subtype; | |
| } | |
| /** | |
| * @override | |
| * @return {*} | |
| */ | |
| get value() { | |
| return this._object.value; | |
| } | |
| /** | |
| * @override | |
| * @return {string|undefined} | |
| */ | |
| get description() { | |
| return this._object.description; | |
| } | |
| /** | |
| * @override | |
| * @return {boolean} | |
| */ | |
| get hasChildren() { | |
| return this._object.hasChildren; | |
| } | |
| /** | |
| * @override | |
| * @return {!Protocol.Runtime.ObjectPreview|undefined} | |
| */ | |
| get preview() { | |
| return this._object.preview; | |
| } | |
| /** | |
| * @override | |
| * @return {number} | |
| */ | |
| arrayLength() { | |
| return this._object.arrayLength(); | |
| } | |
| /** | |
| * @override | |
| * @param {boolean} generatePreview | |
| */ | |
| getOwnProperties(generatePreview) { | |
| return this._object.getOwnProperties(generatePreview); | |
| } | |
| /** | |
| * @override | |
| * @param {boolean} accessorPropertiesOnly | |
| * @param {boolean} generatePreview | |
| * @return {!Promise<!SDK.GetPropertiesResult>} | |
| */ | |
| async getAllProperties(accessorPropertiesOnly, generatePreview) { | |
| const allProperties = await this._object.getAllProperties(accessorPropertiesOnly, generatePreview); | |
| const namesMapping = await Sources.SourceMapNamesResolver._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; | |
| newProperties.push(new SDK.RemoteObjectProperty( | |
| name, property.value, property.enumerable, property.writable, property.isOwn, property.wasThrown, | |
| property.symbol, property.synthetic)); | |
| } | |
| } | |
| return {properties: newProperties, internalProperties: internalProperties}; | |
| } | |
| /** | |
| * @override | |
| * @param {string|!Protocol.Runtime.CallArgument} argumentName | |
| * @param {string} value | |
| * @return {!Promise<string|undefined>} | |
| */ | |
| async setPropertyValue(argumentName, value) { | |
| const namesMapping = await Sources.SourceMapNamesResolver._resolveScope(this._scope); | |
| let name; | |
| if (typeof argumentName === 'string') { | |
| name = argumentName; | |
| } else { | |
| name = /** @type {string} */ (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); | |
| } | |
| /** | |
| * @override | |
| * @param {!Protocol.Runtime.CallArgument} name | |
| * @return {!Promise<string|undefined>} | |
| */ | |
| async deleteProperty(name) { | |
| return this._object.deleteProperty(name); | |
| } | |
| /** | |
| * @override | |
| * @param {function(this:Object, ...)} functionDeclaration | |
| * @param {!Array<!Protocol.Runtime.CallArgument>=} args | |
| * @return {!Promise<!SDK.CallFunctionResult>} | |
| */ | |
| callFunction(functionDeclaration, args) { | |
| return this._object.callFunction(functionDeclaration, args); | |
| } | |
| /** | |
| * @override | |
| * @param {function(this:Object, ...):T} functionDeclaration | |
| * @param {!Array<!Protocol.Runtime.CallArgument>|undefined} args | |
| * @return {!Promise<T>} | |
| * @template T | |
| */ | |
| callFunctionJSON(functionDeclaration, args) { | |
| return this._object.callFunctionJSON(functionDeclaration, args); | |
| } | |
| /** | |
| * @override | |
| */ | |
| release() { | |
| this._object.release(); | |
| } | |
| /** | |
| * @override | |
| * @return {!SDK.DebuggerModel} | |
| */ | |
| debuggerModel() { | |
| return this._object.debuggerModel(); | |
| } | |
| /** | |
| * @override | |
| * @return {!SDK.RuntimeModel} | |
| */ | |
| runtimeModel() { | |
| return this._object.runtimeModel(); | |
| } | |
| /** | |
| * @override | |
| * @return {boolean} | |
| */ | |
| isNode() { | |
| return this._object.isNode(); | |
| } | |
| }; |