| /* |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| "use strict"; |
| |
| (function () { |
| |
| var DebuggerScript = {}; |
| |
| /** @type {!Map<!ScopeType, string>} */ |
| DebuggerScript._scopeTypeNames = new Map(); |
| DebuggerScript._scopeTypeNames.set(ScopeType.Global, "global"); |
| DebuggerScript._scopeTypeNames.set(ScopeType.Local, "local"); |
| DebuggerScript._scopeTypeNames.set(ScopeType.With, "with"); |
| DebuggerScript._scopeTypeNames.set(ScopeType.Closure, "closure"); |
| DebuggerScript._scopeTypeNames.set(ScopeType.Catch, "catch"); |
| DebuggerScript._scopeTypeNames.set(ScopeType.Block, "block"); |
| DebuggerScript._scopeTypeNames.set(ScopeType.Script, "script"); |
| DebuggerScript._scopeTypeNames.set(ScopeType.Eval, "eval"); |
| DebuggerScript._scopeTypeNames.set(ScopeType.Module, "module"); |
| |
| /** |
| * @param {function()} fun |
| * @return {?Array<!Scope>} |
| */ |
| DebuggerScript.getFunctionScopes = function(fun) |
| { |
| var mirror = MakeMirror(fun); |
| if (!mirror.isFunction()) |
| return null; |
| var functionMirror = /** @type {!FunctionMirror} */(mirror); |
| var count = functionMirror.scopeCount(); |
| if (count == 0) |
| return null; |
| var result = []; |
| for (var i = 0; i < count; i++) { |
| var scopeDetails = functionMirror.scope(i).details(); |
| var scopeObject = DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()); |
| if (!scopeObject) |
| continue; |
| result.push({ |
| type: /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeDetails.type())), |
| object: scopeObject, |
| name: scopeDetails.name() || "" |
| }); |
| } |
| return result; |
| } |
| |
| /** |
| * @param {Object} gen |
| * @return {?Array<!Scope>} |
| */ |
| DebuggerScript.getGeneratorScopes = function(gen) |
| { |
| var mirror = MakeMirror(gen); |
| if (!mirror.isGenerator()) |
| return null; |
| var generatorMirror = /** @type {!GeneratorMirror} */(mirror); |
| var count = generatorMirror.scopeCount(); |
| if (count == 0) |
| return null; |
| var result = []; |
| for (var i = 0; i < count; i++) { |
| var scopeDetails = generatorMirror.scope(i).details(); |
| var scopeObject = DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()); |
| if (!scopeObject) |
| continue; |
| result.push({ |
| type: /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeDetails.type())), |
| object: scopeObject, |
| name: scopeDetails.name() || "" |
| }); |
| } |
| return result; |
| } |
| |
| /** |
| * @param {!ExecutionState} execState |
| * @param {!BreakpointInfo} info |
| * @return {string|undefined} |
| */ |
| DebuggerScript.setBreakpoint = function(execState, info) |
| { |
| var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, Debug.BreakPositionAlignment.BreakPosition); |
| var locations = Debug.findBreakPointActualLocations(breakId); |
| if (!locations.length) |
| return undefined; |
| info.lineNumber = locations[0].line; |
| info.columnNumber = locations[0].column; |
| return breakId.toString(); |
| } |
| |
| /** |
| * @param {!ExecutionState} execState |
| * @param {!{breakpointId: number}} info |
| */ |
| DebuggerScript.removeBreakpoint = function(execState, info) |
| { |
| Debug.findBreakPoint(info.breakpointId, true); |
| } |
| |
| /** |
| * @param {!ExecutionState} execState |
| * @param {number} limit |
| * @return {!Array<!JavaScriptCallFrame>} |
| */ |
| DebuggerScript.currentCallFrames = function(execState, limit) |
| { |
| var frames = []; |
| for (var i = 0; i < execState.frameCount() && (!limit || i < limit); ++i) |
| frames.push(DebuggerScript._frameMirrorToJSCallFrame(execState.frame(i))); |
| return frames; |
| } |
| |
| // Returns array in form: |
| // [ 0, <v8_result_report> ] in case of success |
| // or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] in case of compile error, numbers are 1-based. |
| // or throws exception with message. |
| /** |
| * @param {number} scriptId |
| * @param {string} newSource |
| * @param {boolean} preview |
| * @return {!Array<*>} |
| */ |
| DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview) |
| { |
| var scripts = Debug.scripts(); |
| var scriptToEdit = null; |
| for (var i = 0; i < scripts.length; i++) { |
| if (scripts[i].id == scriptId) { |
| scriptToEdit = scripts[i]; |
| break; |
| } |
| } |
| if (!scriptToEdit) |
| throw("Script not found"); |
| |
| var changeLog = []; |
| try { |
| var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog); |
| return [0, result.stack_modified]; |
| } catch (e) { |
| if (e instanceof Debug.LiveEdit.Failure && "details" in e) { |
| var details = /** @type {!LiveEditErrorDetails} */(e.details); |
| if (details.type === "liveedit_compile_error") { |
| var startPosition = details.position.start; |
| return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)]; |
| } |
| } |
| throw e; |
| } |
| } |
| |
| /** |
| * @param {!ExecutionState} execState |
| */ |
| DebuggerScript.clearBreakpoints = function(execState) |
| { |
| Debug.clearAllBreakPoints(); |
| } |
| |
| /** |
| * @param {!Array<!BreakPoint>|undefined} breakpoints |
| */ |
| DebuggerScript.getBreakpointNumbers = function(breakpoints) |
| { |
| var numbers = []; |
| if (!breakpoints) |
| return numbers; |
| |
| for (var i = 0; i < breakpoints.length; i++) { |
| var breakpoint = breakpoints[i]; |
| var scriptBreakPoint = breakpoint.script_break_point(); |
| numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number()); |
| } |
| return numbers; |
| } |
| |
| // NOTE: This function is performance critical, as it can be run on every |
| // statement that generates an async event (like addEventListener) to support |
| // asynchronous call stacks. Thus, when possible, initialize the data lazily. |
| /** |
| * @param {!FrameMirror} frameMirror |
| * @return {!JavaScriptCallFrame} |
| */ |
| DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror) |
| { |
| // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id). |
| // The frameMirror and scopeMirror can be accessed only while paused on the debugger. |
| var frameDetails = frameMirror.details(); |
| |
| var funcObject = frameDetails.func(); |
| var scriptObject = frameDetails.script(); |
| var sourcePosition = frameDetails.sourcePosition(); |
| var thisObject = frameDetails.receiver(); |
| |
| var isAtReturn = !!frameDetails.isAtReturn(); |
| var returnValue = isAtReturn ? frameDetails.returnValue() : undefined; |
| |
| var scopeMirrors = frameMirror.allScopes(false); |
| /** @type {!Array<number>} */ |
| var scopeTypes = new Array(scopeMirrors.length); |
| /** @type {?Array<!Object>} */ |
| var scopeObjects = new Array(scopeMirrors.length); |
| /** @type {!Array<string|undefined>} */ |
| var scopeNames = new Array(scopeMirrors.length); |
| /** @type {?Array<number>} */ |
| var scopeStartPositions = new Array(scopeMirrors.length); |
| /** @type {?Array<number>} */ |
| var scopeEndPositions = new Array(scopeMirrors.length); |
| /** @type {?Array<function()|null>} */ |
| var scopeFunctions = new Array(scopeMirrors.length); |
| for (var i = 0; i < scopeMirrors.length; ++i) { |
| var scopeDetails = scopeMirrors[i].details(); |
| scopeTypes[i] = scopeDetails.type(); |
| scopeObjects[i] = scopeDetails.object(); |
| scopeNames[i] = scopeDetails.name(); |
| scopeStartPositions[i] = scopeDetails.startPosition ? scopeDetails.startPosition() : 0; |
| scopeEndPositions[i] = scopeDetails.endPosition ? scopeDetails.endPosition() : 0; |
| scopeFunctions[i] = scopeDetails.func ? scopeDetails.func() : null; |
| } |
| |
| // Calculated lazily. |
| var scopeChain; |
| var funcMirror; |
| var scriptMirror; |
| var location; |
| /** @type {!Array<?RawLocation>} */ |
| var scopeStartLocations; |
| /** @type {!Array<?RawLocation>} */ |
| var scopeEndLocations; |
| var details; |
| |
| /** |
| * @param {!ScriptMirror|undefined} script |
| * @param {number} pos |
| * @return {?RawLocation} |
| */ |
| function createLocation(script, pos) |
| { |
| if (!script) |
| return null; |
| |
| var location = script.locationFromPosition(pos, true); |
| return { |
| "lineNumber": location.line, |
| "columnNumber": location.column, |
| "scriptId": String(script.id()) |
| } |
| } |
| |
| /** |
| * @return {!Array<!Object>} |
| */ |
| function ensureScopeChain() |
| { |
| if (!scopeChain) { |
| scopeChain = []; |
| scopeStartLocations = []; |
| scopeEndLocations = []; |
| for (var i = 0, j = 0; i < scopeObjects.length; ++i) { |
| var scopeObject = DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i]); |
| if (scopeObject) { |
| scopeTypes[j] = scopeTypes[i]; |
| scopeNames[j] = scopeNames[i]; |
| scopeChain[j] = scopeObject; |
| |
| var funcMirror = scopeFunctions ? MakeMirror(scopeFunctions[i]) : null; |
| if (!funcMirror || !funcMirror.isFunction()) |
| funcMirror = new UnresolvedFunctionMirror(funcObject); |
| |
| var script = /** @type {!FunctionMirror} */(funcMirror).script(); |
| scopeStartLocations[j] = createLocation(script, scopeStartPositions[i]); |
| scopeEndLocations[j] = createLocation(script, scopeEndPositions[i]); |
| ++j; |
| } |
| } |
| scopeTypes.length = scopeChain.length; |
| scopeNames.length = scopeChain.length; |
| scopeObjects = null; // Free for GC. |
| scopeFunctions = null; |
| scopeStartPositions = null; |
| scopeEndPositions = null; |
| } |
| return scopeChain; |
| } |
| |
| /** |
| * @return {!JavaScriptCallFrameDetails} |
| */ |
| function lazyDetails() |
| { |
| if (!details) { |
| var scopeObjects = ensureScopeChain(); |
| var script = ensureScriptMirror(); |
| /** @type {!Array<Scope>} */ |
| var scopes = []; |
| for (var i = 0; i < scopeObjects.length; ++i) { |
| var scope = { |
| "type": /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeTypes[i])), |
| "object": scopeObjects[i], |
| }; |
| if (scopeNames[i]) |
| scope.name = scopeNames[i]; |
| if (scopeStartLocations[i]) |
| scope.startLocation = /** @type {!RawLocation} */(scopeStartLocations[i]); |
| if (scopeEndLocations[i]) |
| scope.endLocation = /** @type {!RawLocation} */(scopeEndLocations[i]); |
| scopes.push(scope); |
| } |
| details = { |
| "functionName": ensureFuncMirror().debugName(), |
| "location": { |
| "lineNumber": ensureLocation().line, |
| "columnNumber": ensureLocation().column, |
| "scriptId": String(script.id()) |
| }, |
| "this": thisObject, |
| "scopeChain": scopes |
| }; |
| var functionLocation = ensureFuncMirror().sourceLocation(); |
| if (functionLocation) { |
| details.functionLocation = { |
| "lineNumber": functionLocation.line, |
| "columnNumber": functionLocation.column, |
| "scriptId": String(script.id()) |
| }; |
| } |
| if (isAtReturn) |
| details.returnValue = returnValue; |
| } |
| return details; |
| } |
| |
| /** |
| * @return {!FunctionMirror} |
| */ |
| function ensureFuncMirror() |
| { |
| if (!funcMirror) { |
| funcMirror = MakeMirror(funcObject); |
| if (!funcMirror.isFunction()) |
| funcMirror = new UnresolvedFunctionMirror(funcObject); |
| } |
| return /** @type {!FunctionMirror} */(funcMirror); |
| } |
| |
| /** |
| * @return {!ScriptMirror} |
| */ |
| function ensureScriptMirror() |
| { |
| if (!scriptMirror) { |
| scriptMirror = MakeMirror(scriptObject); |
| } |
| return /** @type {!ScriptMirror} */(scriptMirror); |
| } |
| |
| /** |
| * @return {!{line: number, column: number}} |
| */ |
| function ensureLocation() |
| { |
| if (!location) { |
| var script = ensureScriptMirror(); |
| location = script.locationFromPosition(sourcePosition, true); |
| if (!location) |
| location = { line: 0, column: 0 }; |
| } |
| return location; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| function contextId() |
| { |
| var mirror = ensureFuncMirror(); |
| var context = mirror.context(); |
| if (context && context.data()) |
| return Number(context.data()); |
| return 0; |
| } |
| |
| /** |
| * @param {string} expression |
| * @param {boolean} throwOnSideEffect |
| * @return {*} |
| */ |
| function evaluate(expression, throwOnSideEffect) |
| { |
| return frameMirror.evaluate(expression, throwOnSideEffect).value(); |
| } |
| |
| /** @return {undefined} */ |
| function restart() |
| { |
| return frameMirror.restart(); |
| } |
| |
| /** |
| * @param {number} scopeNumber |
| * @param {string} variableName |
| * @param {*} newValue |
| */ |
| function setVariableValue(scopeNumber, variableName, newValue) |
| { |
| var scopeMirror = frameMirror.scope(scopeNumber); |
| if (!scopeMirror) |
| throw new Error("Incorrect scope index"); |
| scopeMirror.setVariableValue(variableName, newValue); |
| } |
| |
| return { |
| "contextId": contextId, |
| "thisObject": thisObject, |
| "evaluate": evaluate, |
| "restart": restart, |
| "setVariableValue": setVariableValue, |
| "isAtReturn": isAtReturn, |
| "details": lazyDetails |
| }; |
| } |
| |
| /** |
| * @param {number} scopeType |
| * @param {!Object} scopeObject |
| * @return {!Object|undefined} |
| */ |
| DebuggerScript._buildScopeObject = function(scopeType, scopeObject) |
| { |
| var result; |
| switch (scopeType) { |
| case ScopeType.Local: |
| case ScopeType.Closure: |
| case ScopeType.Catch: |
| case ScopeType.Block: |
| case ScopeType.Script: |
| case ScopeType.Eval: |
| case ScopeType.Module: |
| // For transient objects we create a "persistent" copy that contains |
| // the same properties. |
| // Reset scope object prototype to null so that the proto properties |
| // don't appear in the local scope section. |
| var properties = /** @type {!ObjectMirror} */(MakeMirror(scopeObject)).properties(); |
| // Almost always Script scope will be empty, so just filter out that noise. |
| // Also drop empty Block, Eval and Script scopes, should we get any. |
| if (!properties.length && (scopeType === ScopeType.Script || |
| scopeType === ScopeType.Block || |
| scopeType === ScopeType.Eval || |
| scopeType === ScopeType.Module)) { |
| break; |
| } |
| result = { __proto__: null }; |
| for (var j = 0; j < properties.length; j++) { |
| var name = properties[j].name(); |
| if (name.length === 0 || name.charAt(0) === ".") |
| continue; // Skip internal variables like ".arguments" and variables with empty name |
| result[name] = properties[j].value_; |
| } |
| break; |
| case ScopeType.Global: |
| case ScopeType.With: |
| result = scopeObject; |
| break; |
| } |
| return result; |
| } |
| |
| return DebuggerScript; |
| })(); |