/* * 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. */ (function () { var DebuggerScript = {}; DebuggerScript.PauseOnExceptionsState = { DontPauseOnExceptions: 0, PauseOnAllExceptions: 1, PauseOnUncaughtExceptions: 2 }; DebuggerScript.ScopeInfoDetails = { AllScopes: 0, FastAsyncScopes: 1, NoScopes: 2 }; DebuggerScript._pauseOnExceptionsState = DebuggerScript.PauseOnExceptionsState.DontPauseOnExceptions; Debug.clearBreakOnException(); Debug.clearBreakOnUncaughtException(); DebuggerScript.getAfterCompileScript = function(eventData) { return DebuggerScript._formatScript(eventData.script_.script_); } DebuggerScript.getWorkerScripts = function() { var result = []; var scripts = Debug.scripts(); for (var i = 0; i < scripts.length; ++i) { var script = scripts[i]; // Workers don't share same V8 heap now so there is no need to complicate stuff with // the context id like we do to discriminate between scripts from different pages. // However we need to filter out v8 native scripts. if (script.context_data && script.context_data === "worker") result.push(DebuggerScript._formatScript(script)); } return result; } DebuggerScript.getFunctionScopes = function(fun) { var mirror = MakeMirror(fun); var count = mirror.scopeCount(); if (count == 0) return null; var result = []; for (var i = 0; i < count; i++) { var scopeDetails = mirror.scope(i).details(); var scopeObject = DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()); if (!scopeObject) continue; result.push({ type: scopeDetails.type(), object: scopeObject }); } return result; } DebuggerScript.getCollectionEntries = function(object) { var mirror = MakeMirror(object, true /* transient */); if (mirror.isMap()) return mirror.entries(); if (mirror.isSet()) { var result = []; var values = mirror.values(); for (var i = 0; i < values.length; ++i) result.push({ value: values[i] }); return result; } } DebuggerScript.getInternalProperties = function(value) { var properties = ObjectMirror.GetInternalProperties(value); var result = []; for (var i = 0; i < properties.length; i++) { var mirror = properties[i]; result.push({ name: mirror.name(), value: mirror.value().value() }); } return result; } DebuggerScript.setFunctionVariableValue = function(functionValue, scopeIndex, variableName, newValue) { var mirror = MakeMirror(functionValue); if (!mirror.isFunction()) throw new Error("Function value has incorrect type"); return DebuggerScript._setScopeVariableValue(mirror, scopeIndex, variableName, newValue); } DebuggerScript._setScopeVariableValue = function(scopeHolder, scopeIndex, variableName, newValue) { var scopeMirror = scopeHolder.scope(scopeIndex); if (!scopeMirror) throw new Error("Incorrect scope index"); scopeMirror.setVariableValue(variableName, newValue); return undefined; } DebuggerScript.getScripts = function(contextData) { var result = []; if (!contextData) return result; var comma = contextData.indexOf(","); if (comma === -1) return result; // Context data is a string in the following format: // ("page"|"injected")"," var idSuffix = contextData.substring(comma); // including the comma var scripts = Debug.scripts(); for (var i = 0; i < scripts.length; ++i) { var script = scripts[i]; if (script.context_data && script.context_data.lastIndexOf(idSuffix) != -1) result.push(DebuggerScript._formatScript(script)); } return result; } DebuggerScript._formatScript = function(script) { var lineEnds = script.line_ends; var lineCount = lineEnds.length; var endLine = script.line_offset + lineCount - 1; var endColumn; // V8 will not count last line if script source ends with \n. if (script.source[script.source.length - 1] === '\n') { endLine += 1; endColumn = 0; } else { if (lineCount === 1) endColumn = script.source.length + script.column_offset; else endColumn = script.source.length - (lineEnds[lineCount - 2] + 1); } return { id: script.id, name: script.nameOrSourceURL(), sourceURL: script.source_url, sourceMappingURL: script.source_mapping_url, source: script.source, startLine: script.line_offset, startColumn: script.column_offset, endLine: endLine, endColumn: endColumn, isContentScript: !!script.context_data && script.context_data.indexOf("injected") == 0 }; } DebuggerScript.setBreakpoint = function(execState, info) { var positionAlignment = info.interstatementLocation ? Debug.BreakPositionAlignment.BreakPosition : Debug.BreakPositionAlignment.Statement; var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, positionAlignment); var locations = Debug.findBreakPointActualLocations(breakId); if (!locations.length) return undefined; info.lineNumber = locations[0].line; info.columnNumber = locations[0].column; return breakId.toString(); } DebuggerScript.removeBreakpoint = function(execState, info) { Debug.findBreakPoint(info.breakpointId, true); } DebuggerScript.pauseOnExceptionsState = function() { return DebuggerScript._pauseOnExceptionsState; } DebuggerScript.setPauseOnExceptionsState = function(newState) { DebuggerScript._pauseOnExceptionsState = newState; if (DebuggerScript.PauseOnExceptionsState.PauseOnAllExceptions === newState) Debug.setBreakOnException(); else Debug.clearBreakOnException(); if (DebuggerScript.PauseOnExceptionsState.PauseOnUncaughtExceptions === newState) Debug.setBreakOnUncaughtException(); else Debug.clearBreakOnUncaughtException(); } DebuggerScript.frameCount = function(execState) { return execState.frameCount(); } DebuggerScript.currentCallFrame = function(execState, data) { var maximumLimit = data >> 2; var scopeDetailsLevel = data & 3; var frameCount = execState.frameCount(); if (maximumLimit && maximumLimit < frameCount) frameCount = maximumLimit; var topFrame = undefined; for (var i = frameCount - 1; i >= 0; i--) { var frameMirror = execState.frame(i); topFrame = DebuggerScript._frameMirrorToJSCallFrame(frameMirror, topFrame, scopeDetailsLevel); } return topFrame; } DebuggerScript.currentCallFrameByIndex = function(execState, index) { if (index < 0) return undefined; var frameCount = execState.frameCount(); if (index >= frameCount) return undefined; return DebuggerScript._frameMirrorToJSCallFrame(execState.frame(index), undefined, DebuggerScript.ScopeInfoDetails.NoScopes); } DebuggerScript.stepIntoStatement = function(execState) { execState.prepareStep(Debug.StepAction.StepIn, 1); } DebuggerScript.stepOverStatement = function(execState, callFrame) { execState.prepareStep(Debug.StepAction.StepNext, 1); } DebuggerScript.stepOutOfFunction = function(execState, callFrame) { execState.prepareStep(Debug.StepAction.StepOut, 1); } // Returns array in form: // [ 0, ] in case of success // or [ 1, , , , ] in case of compile error, numbers are 1-based. // or throws exception with message. 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]; } catch (e) { if (e instanceof Debug.LiveEdit.Failure && "details" in e) { var details = 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; } } DebuggerScript.clearBreakpoints = function(execState, info) { Debug.clearAllBreakPoints(); } DebuggerScript.setBreakpointsActivated = function(execState, info) { Debug.debuggerFlags().breakPointsActive.setValue(info.enabled); } DebuggerScript.getScriptSource = function(eventData) { return eventData.script().source(); } DebuggerScript.setScriptSource = function(eventData, source) { if (eventData.script().data() === "injected-script") return; eventData.script().setSource(source); } DebuggerScript.getScriptName = function(eventData) { return eventData.script().script_.nameOrSourceURL(); } DebuggerScript.getBreakpointNumbers = function(eventData) { var breakpoints = eventData.breakPointsHit(); 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; } DebuggerScript.isEvalCompilation = function(eventData) { var script = eventData.script(); return (script.compilationType() === Debug.ScriptCompilationType.Eval); } // 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. DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame, scopeDetailsLevel) { // 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 sourcePosition = frameDetails.sourcePosition(); var thisObject = frameDetails.receiver(); var isAtReturn = !!frameDetails.isAtReturn(); var returnValue = isAtReturn ? frameDetails.returnValue() : undefined; var scopeMirrors = (scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.NoScopes ? [] : frameMirror.allScopes(scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.FastAsyncScopes)); var scopeTypes = new Array(scopeMirrors.length); var scopeObjects = 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(); } // Calculated lazily. var scopeChain; var funcMirror; var location; function lazyScopeChain() { if (!scopeChain) { scopeChain = []; for (var i = 0, j = 0; i < scopeObjects.length; ++i) { var scopeObject = DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i]); if (scopeObject) { scopeTypes[j] = scopeTypes[i]; scopeChain[j] = scopeObject; ++j; } } scopeTypes.length = scopeChain.length; scopeObjects = null; // Free for GC. } return scopeChain; } function lazyScopeTypes() { if (!scopeChain) lazyScopeChain(); return scopeTypes; } function ensureFuncMirror() { if (!funcMirror) { funcMirror = MakeMirror(funcObject); if (!funcMirror.isFunction()) funcMirror = new UnresolvedFunctionMirror(funcObject); } return funcMirror; } function ensureLocation() { if (!location) { var script = ensureFuncMirror().script(); if (script) location = script.locationFromPosition(sourcePosition, true); if (!location) location = { line: 0, column: 0 }; } return location; } function line() { return ensureLocation().line; } function column() { return ensureLocation().column; } function sourceID() { var script = ensureFuncMirror().script(); return script && script.id(); } function scriptName() { var script = ensureFuncMirror().script(); return script && script.name(); } function functionName() { var func = ensureFuncMirror(); if (!func.resolved()) return undefined; var displayName; var valueMirror = func.property("displayName").value(); if (valueMirror && valueMirror.isString()) displayName = valueMirror.value(); return displayName || func.name() || func.inferredName(); } function evaluate(expression) { return frameMirror.evaluate(expression, false).value(); } function restart() { return Debug.LiveEdit.RestartFrame(frameMirror); } function setVariableValue(scopeNumber, variableName, newValue) { return DebuggerScript._setScopeVariableValue(frameMirror, scopeNumber, variableName, newValue); } function stepInPositions() { var stepInPositionsV8 = frameMirror.stepInPositions(); var stepInPositionsProtocol; if (stepInPositionsV8) { stepInPositionsProtocol = []; var script = ensureFuncMirror().script(); if (script) { var scriptId = String(script.id()); for (var i = 0; i < stepInPositionsV8.length; i++) { var item = { scriptId: scriptId, lineNumber: stepInPositionsV8[i].position.line, columnNumber: stepInPositionsV8[i].position.column }; stepInPositionsProtocol.push(item); } } } return JSON.stringify(stepInPositionsProtocol); } return { "sourceID": sourceID, "line": line, "column": column, "scriptName": scriptName, "functionName": functionName, "thisObject": thisObject, "scopeChain": lazyScopeChain, "scopeType": lazyScopeTypes, "evaluate": evaluate, "caller": callerFrame, "restart": restart, "setVariableValue": setVariableValue, "stepInPositions": stepInPositions, "isAtReturn": isAtReturn, "returnValue": returnValue }; } DebuggerScript._buildScopeObject = function(scopeType, scopeObject) { var result; switch (scopeType) { case ScopeType.Local: case ScopeType.Closure: case ScopeType.Catch: // 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. result = { __proto__: null }; var properties = MakeMirror(scopeObject, true /* transient */).properties(); for (var j = 0; j < properties.length; j++) { var name = properties[j].name(); if (name.charAt(0) === ".") continue; // Skip internal variables like ".arguments" result[name] = properties[j].value_; } break; case ScopeType.Global: case ScopeType.With: result = scopeObject; break; case ScopeType.Block: // Unsupported yet. Mustn't be reachable. break; } return result; } DebuggerScript.getPromiseDetails = function(eventData) { return { "promise": eventData.promise().value(), "parentPromise": eventData.parentPromise().value(), "status": eventData.status() }; } // We never resolve Mirror by its handle so to avoid memory leaks caused by Mirrors in the cache we disable it. ToggleMirrorCache(false); return DebuggerScript; })();