| // Copyright 2016 the V8 project authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| "use strict"; | |
| // If true, prints all messages sent and received by inspector. | |
| const printProtocolMessages = false; | |
| // The active wrapper instance. | |
| let activeWrapper = undefined; | |
| // Receiver function called by inspector, delegating to active wrapper. | |
| function receive(message) { | |
| activeWrapper.receiveMessage(message); | |
| } | |
| class DebugWrapper { | |
| constructor() { | |
| // Message dictionary storing {id, message} pairs. | |
| this.receivedMessages = new Map(); | |
| // Each message dispatched by the Debug wrapper is assigned a unique number | |
| // using nextMessageId. | |
| this.nextMessageId = 0; | |
| // The listener method called on certain events. | |
| this.listener = undefined; | |
| // Debug events which can occur in the V8 JavaScript engine. | |
| this.DebugEvent = { Break: 1, | |
| Exception: 2, | |
| AfterCompile: 3, | |
| CompileError: 4, | |
| OOM: 5, | |
| }; | |
| // The different types of steps. | |
| this.StepAction = { StepOut: 0, | |
| StepNext: 1, | |
| StepIn: 2, | |
| }; | |
| // The different types of scripts matching enum ScriptType in objects.h. | |
| this.ScriptType = { Native: 0, | |
| Extension: 1, | |
| Normal: 2, | |
| Wasm: 3, | |
| Inspector: 4, | |
| }; | |
| // A copy of the scope types from runtime-debug.cc. | |
| // NOTE: these constants should be backward-compatible, so | |
| // add new ones to the end of this list. | |
| this.ScopeType = { Global: 0, | |
| Local: 1, | |
| With: 2, | |
| Closure: 3, | |
| Catch: 4, | |
| Block: 5, | |
| Script: 6, | |
| Eval: 7, | |
| Module: 8 | |
| }; | |
| // Types of exceptions that can be broken upon. | |
| this.ExceptionBreak = { Caught : 0, | |
| Uncaught: 1 }; | |
| // The different script break point types. | |
| this.ScriptBreakPointType = { ScriptId: 0, | |
| ScriptName: 1, | |
| ScriptRegExp: 2 }; | |
| // Store the current script id so we can skip corresponding break events. | |
| this.thisScriptId = %FunctionGetScriptId(receive); | |
| // Stores all set breakpoints. | |
| this.breakpoints = new Set(); | |
| // Register as the active wrapper. | |
| assertTrue(activeWrapper === undefined); | |
| activeWrapper = this; | |
| } | |
| enable() { this.sendMessageForMethodChecked("Debugger.enable"); } | |
| disable() { this.sendMessageForMethodChecked("Debugger.disable"); } | |
| setListener(listener) { this.listener = listener; } | |
| stepOver() { this.sendMessageForMethodChecked("Debugger.stepOver"); } | |
| stepInto() { this.sendMessageForMethodChecked("Debugger.stepInto"); } | |
| stepOut() { this.sendMessageForMethodChecked("Debugger.stepOut"); } | |
| setBreakOnException() { | |
| this.sendMessageForMethodChecked( | |
| "Debugger.setPauseOnExceptions", { state : "all" }); | |
| } | |
| clearBreakOnException() { | |
| const newState = this.isBreakOnUncaughtException() ? "uncaught" : "none"; | |
| this.sendMessageForMethodChecked( | |
| "Debugger.setPauseOnExceptions", { state : newState }); | |
| } | |
| isBreakOnException() { | |
| return !!%IsBreakOnException(this.ExceptionBreak.Caught); | |
| }; | |
| setBreakOnUncaughtException() { | |
| const newState = this.isBreakOnException() ? "all" : "uncaught"; | |
| this.sendMessageForMethodChecked( | |
| "Debugger.setPauseOnExceptions", { state : newState }); | |
| } | |
| clearBreakOnUncaughtException() { | |
| const newState = this.isBreakOnException() ? "all" : "none"; | |
| this.sendMessageForMethodChecked( | |
| "Debugger.setPauseOnExceptions", { state : newState }); | |
| } | |
| isBreakOnUncaughtException() { | |
| return !!%IsBreakOnException(this.ExceptionBreak.Uncaught); | |
| }; | |
| clearStepping() { %ClearStepping(); }; | |
| // Returns the resulting breakpoint id. | |
| setBreakPoint(func, opt_line, opt_column, opt_condition) { | |
| assertTrue(%IsFunction(func)); | |
| assertFalse(%FunctionIsAPIFunction(func)); | |
| const scriptid = %FunctionGetScriptId(func); | |
| assertTrue(scriptid != -1); | |
| const offset = %FunctionGetScriptSourcePosition(func); | |
| const loc = | |
| %ScriptLocationFromLine2(scriptid, opt_line, opt_column, offset); | |
| return this.setBreakPointAtLocation(scriptid, loc, opt_condition); | |
| } | |
| setScriptBreakPoint(type, scriptid, opt_line, opt_column, opt_condition) { | |
| // Only sets by script id are supported for now. | |
| assertEquals(this.ScriptBreakPointType.ScriptId, type); | |
| return this.setScriptBreakPointById(scriptid, opt_line, opt_column, | |
| opt_condition); | |
| } | |
| setScriptBreakPointById(scriptid, opt_line, opt_column, opt_condition) { | |
| const loc = %ScriptLocationFromLine2(scriptid, opt_line, opt_column, 0); | |
| return this.setBreakPointAtLocation(scriptid, loc, opt_condition); | |
| } | |
| setBreakPointByScriptIdAndPosition(scriptid, position) { | |
| const loc = %ScriptPositionInfo2(scriptid, position, false); | |
| return this.setBreakPointAtLocation(scriptid, loc, undefined); | |
| } | |
| clearBreakPoint(breakpoint) { | |
| assertTrue(this.breakpoints.has(breakpoint)); | |
| const breakid = breakpoint.id; | |
| const {msgid, msg} = this.createMessage( | |
| "Debugger.removeBreakpoint", { breakpointId : breakid }); | |
| this.sendMessage(msg); | |
| this.takeReplyChecked(msgid); | |
| this.breakpoints.delete(breakid); | |
| } | |
| clearAllBreakPoints() { | |
| for (let breakpoint of this.breakpoints) { | |
| this.clearBreakPoint(breakpoint); | |
| } | |
| this.breakpoints.clear(); | |
| } | |
| showBreakPoints(f) { | |
| if (!%IsFunction(f)) throw new Error("Not passed a Function"); | |
| const source = %FunctionGetSourceCode(f); | |
| const offset = %FunctionGetScriptSourcePosition(f); | |
| const locations = %GetBreakLocations(f); | |
| if (!locations) return source; | |
| locations.sort(function(x, y) { return x - y; }); | |
| let result = ""; | |
| let prev_pos = 0; | |
| let pos; | |
| for (var i = 0; i < locations.length; i++) { | |
| pos = locations[i] - offset; | |
| result += source.slice(prev_pos, pos); | |
| result += "[B" + i + "]"; | |
| prev_pos = pos; | |
| } | |
| pos = source.length; | |
| result += source.substring(prev_pos, pos); | |
| return result; | |
| } | |
| debuggerFlags() { | |
| return { breakPointsActive : | |
| { setValue : (enabled) => this.setBreakPointsActive(enabled) } | |
| }; | |
| } | |
| scripts() { | |
| // Collect all scripts in the heap. | |
| return %DebugGetLoadedScripts(); | |
| } | |
| // Returns a Script object. If the parameter is a function the return value | |
| // is the script in which the function is defined. If the parameter is a | |
| // string the return value is the script for which the script name has that | |
| // string value. If it is a regexp and there is a unique script whose name | |
| // matches we return that, otherwise undefined. | |
| findScript(func_or_script_name) { | |
| if (%IsFunction(func_or_script_name)) { | |
| return %FunctionGetScript(func_or_script_name); | |
| } else if (%IsRegExp(func_or_script_name)) { | |
| var scripts = this.scripts(); | |
| var last_result = null; | |
| var result_count = 0; | |
| for (var i in scripts) { | |
| var script = scripts[i]; | |
| if (func_or_script_name.test(script.name)) { | |
| last_result = script; | |
| result_count++; | |
| } | |
| } | |
| // Return the unique script matching the regexp. If there are more | |
| // than one we don't return a value since there is no good way to | |
| // decide which one to return. Returning a "random" one, say the | |
| // first, would introduce nondeterminism (or something close to it) | |
| // because the order is the heap iteration order. | |
| if (result_count == 1) { | |
| return last_result; | |
| } else { | |
| return undefined; | |
| } | |
| } else { | |
| return %GetScript(func_or_script_name); | |
| } | |
| } | |
| // Returns the script source. If the parameter is a function the return value | |
| // is the script source for the script in which the function is defined. If the | |
| // parameter is a string the return value is the script for which the script | |
| // name has that string value. | |
| scriptSource(func_or_script_name) { | |
| return this.findScript(func_or_script_name).source; | |
| }; | |
| sourcePosition(f) { | |
| if (!%IsFunction(f)) throw new Error("Not passed a Function"); | |
| return %FunctionGetScriptSourcePosition(f); | |
| }; | |
| // Returns the character position in a script based on a line number and an | |
| // optional position within that line. | |
| findScriptSourcePosition(script, opt_line, opt_column) { | |
| var location = %ScriptLocationFromLine(script, opt_line, opt_column, 0); | |
| return location ? location.position : null; | |
| }; | |
| findFunctionSourceLocation(func, opt_line, opt_column) { | |
| var script = %FunctionGetScript(func); | |
| var script_offset = %FunctionGetScriptSourcePosition(func); | |
| return %ScriptLocationFromLine(script, opt_line, opt_column, script_offset); | |
| } | |
| setBreakPointsActive(enabled) { | |
| const {msgid, msg} = this.createMessage( | |
| "Debugger.setBreakpointsActive", { active : enabled }); | |
| this.sendMessage(msg); | |
| this.takeReplyChecked(msgid); | |
| } | |
| generatorScopeCount(gen) { | |
| return %GetGeneratorScopeCount(gen); | |
| } | |
| generatorScope(gen, index) { | |
| // These indexes correspond definitions in debug-scopes.h. | |
| const kScopeDetailsTypeIndex = 0; | |
| const kScopeDetailsObjectIndex = 1; | |
| const details = %GetGeneratorScopeDetails(gen, index); | |
| function scopeObjectProperties() { | |
| const obj = details[kScopeDetailsObjectIndex]; | |
| return Object.keys(obj).map((k, v) => v); | |
| } | |
| function setScopeVariableValue(name, value) { | |
| const res = %SetScopeVariableValue(gen, null, null, index, name, value); | |
| if (!res) throw new Error("Failed to set variable value"); | |
| } | |
| const scopeObject = | |
| { value : () => details[kScopeDetailsObjectIndex], | |
| property : (prop) => details[kScopeDetailsObjectIndex][prop], | |
| properties : scopeObjectProperties, | |
| propertyNames : () => Object.keys(details[kScopeDetailsObjectIndex]) | |
| .map((key, _) => key), | |
| }; | |
| return { scopeType : () => details[kScopeDetailsTypeIndex], | |
| scopeIndex : () => index, | |
| scopeObject : () => scopeObject, | |
| setVariableValue : setScopeVariableValue, | |
| } | |
| } | |
| generatorScopes(gen) { | |
| const count = %GetGeneratorScopeCount(gen); | |
| const scopes = []; | |
| for (let i = 0; i < count; i++) { | |
| scopes.push(this.generatorScope(gen, i)); | |
| } | |
| return scopes; | |
| } | |
| get LiveEdit() { | |
| const debugContext = %GetDebugContext(); | |
| return debugContext.Debug.LiveEdit; | |
| } | |
| // --- Internal methods. ----------------------------------------------------- | |
| getNextMessageId() { | |
| return this.nextMessageId++; | |
| } | |
| createMessage(method, params) { | |
| const id = this.getNextMessageId(); | |
| const msg = JSON.stringify({ | |
| id: id, | |
| method: method, | |
| params: params, | |
| }); | |
| return { msgid : id, msg: msg }; | |
| } | |
| receiveMessage(message) { | |
| const parsedMessage = JSON.parse(message); | |
| if (printProtocolMessages) { | |
| print(JSON.stringify(parsedMessage, undefined, 1)); | |
| } | |
| if (parsedMessage.id !== undefined) { | |
| this.receivedMessages.set(parsedMessage.id, parsedMessage); | |
| } | |
| this.dispatchMessage(parsedMessage); | |
| } | |
| sendMessage(message) { | |
| if (printProtocolMessages) print(message); | |
| send(message); | |
| } | |
| sendMessageForMethodChecked(method, params) { | |
| const {msgid, msg} = this.createMessage(method, params); | |
| this.sendMessage(msg); | |
| this.takeReplyChecked(msgid); | |
| } | |
| takeReplyChecked(msgid) { | |
| const reply = this.receivedMessages.get(msgid); | |
| assertTrue(reply !== undefined); | |
| this.receivedMessages.delete(msgid); | |
| return reply; | |
| } | |
| setBreakPointAtLocation(scriptid, loc, opt_condition) { | |
| const params = { location : | |
| { scriptId : scriptid.toString(), | |
| lineNumber : loc.line, | |
| columnNumber : loc.column, | |
| }, | |
| condition : opt_condition, | |
| }; | |
| const {msgid, msg} = this.createMessage("Debugger.setBreakpoint", params); | |
| this.sendMessage(msg); | |
| const reply = this.takeReplyChecked(msgid); | |
| const result = reply.result; | |
| assertTrue(result !== undefined); | |
| const breakid = result.breakpointId; | |
| assertTrue(breakid !== undefined); | |
| const actualLoc = %ScriptLocationFromLine2(scriptid, | |
| result.actualLocation.lineNumber, result.actualLocation.columnNumber, | |
| 0); | |
| const breakpoint = { id : result.breakpointId, | |
| actual_position : actualLoc.position, | |
| } | |
| this.breakpoints.add(breakpoint); | |
| return breakpoint; | |
| } | |
| execStatePrepareStep(action) { | |
| switch(action) { | |
| case this.StepAction.StepOut: this.stepOut(); break; | |
| case this.StepAction.StepNext: this.stepOver(); break; | |
| case this.StepAction.StepIn: this.stepInto(); break; | |
| default: %AbortJS("Unsupported StepAction"); break; | |
| } | |
| } | |
| execStateScopeType(type) { | |
| switch (type) { | |
| case "global": return this.ScopeType.Global; | |
| case "local": return this.ScopeType.Local; | |
| case "with": return this.ScopeType.With; | |
| case "closure": return this.ScopeType.Closure; | |
| case "catch": return this.ScopeType.Catch; | |
| case "block": return this.ScopeType.Block; | |
| case "script": return this.ScopeType.Script; | |
| case "eval": return this.ScopeType.Eval; | |
| case "module": return this.ScopeType.Module; | |
| default: %AbortJS("Unexpected scope type"); | |
| } | |
| } | |
| execStateScopeObjectProperty(serialized_scope, prop) { | |
| let found = null; | |
| for (let i = 0; i < serialized_scope.length; i++) { | |
| const elem = serialized_scope[i]; | |
| if (elem.name == prop) { | |
| found = elem; | |
| break; | |
| } | |
| } | |
| if (found == null) return { isUndefined : () => true }; | |
| const val = { value : () => found.value.value }; | |
| // Not undefined in the sense that we did find a property, even though | |
| // the value can be 'undefined'. | |
| return { value : () => val, | |
| isUndefined : () => false, | |
| }; | |
| } | |
| // Returns an array of property descriptors of the scope object. | |
| // This is in contrast to the original API, which simply passed object | |
| // mirrors. | |
| execStateScopeObject(obj) { | |
| const serialized_scope = this.getProperties(obj.objectId); | |
| const scope = this.propertiesToObject(serialized_scope); | |
| return { value : () => scope, | |
| property : (prop) => | |
| this.execStateScopeObjectProperty(serialized_scope, prop), | |
| properties : () => serialized_scope.map(elem => elem.value), | |
| propertyNames : () => serialized_scope.map(elem => elem.name) | |
| }; | |
| } | |
| execStateScopeDetails(scope) { | |
| var start_position; | |
| var end_position | |
| const start = scope.startLocation; | |
| const end = scope.endLocation; | |
| if (start) { | |
| start_position = %ScriptLocationFromLine2( | |
| parseInt(start.scriptId), start.lineNumber, start.columnNumber, 0) | |
| .position; | |
| } | |
| if (end) { | |
| end_position = %ScriptLocationFromLine2( | |
| parseInt(end.scriptId), end.lineNumber, end.columnNumber, 0) | |
| .position; | |
| } | |
| return { name : () => scope.name, | |
| startPosition : () => start_position, | |
| endPosition : () => end_position | |
| }; | |
| } | |
| setVariableValue(frame, scope_index, name, value) { | |
| const frameid = frame.callFrameId; | |
| const {msgid, msg} = this.createMessage( | |
| "Debugger.setVariableValue", | |
| { callFrameId : frameid, | |
| scopeNumber : scope_index, | |
| variableName : name, | |
| newValue : { value : value } | |
| }); | |
| this.sendMessage(msg); | |
| const reply = this.takeReplyChecked(msgid); | |
| if (reply.error) { | |
| throw new Error("Failed to set variable value"); | |
| } | |
| } | |
| execStateScope(frame, scope_index) { | |
| const scope = frame.scopeChain[scope_index]; | |
| return { scopeType : () => this.execStateScopeType(scope.type), | |
| scopeIndex : () => scope_index, | |
| frameIndex : () => frame.callFrameId, | |
| scopeObject : () => this.execStateScopeObject(scope.object), | |
| setVariableValue : | |
| (name, value) => this.setVariableValue(frame, scope_index, | |
| name, value), | |
| details : () => this.execStateScopeDetails(scope) | |
| }; | |
| } | |
| // Takes a list of properties as produced by getProperties and turns them | |
| // into an object. | |
| propertiesToObject(props) { | |
| const obj = {} | |
| props.forEach((elem) => { | |
| const key = elem.name; | |
| let value; | |
| if (elem.value) { | |
| // Some properties (e.g. with getters/setters) don't have a value. | |
| switch (elem.value.type) { | |
| case "undefined": value = undefined; break; | |
| default: value = elem.value.value; break; | |
| } | |
| } | |
| obj[key] = value; | |
| }) | |
| return obj; | |
| } | |
| getProperties(objectId) { | |
| const {msgid, msg} = this.createMessage( | |
| "Runtime.getProperties", { objectId : objectId, ownProperties: true }); | |
| this.sendMessage(msg); | |
| const reply = this.takeReplyChecked(msgid); | |
| return reply.result.result; | |
| } | |
| getLocalScopeDetails(frame) { | |
| const scopes = frame.scopeChain; | |
| for (let i = 0; i < scopes.length; i++) { | |
| const scope = scopes[i] | |
| if (scope.type == "local") { | |
| return this.getProperties(scope.object.objectId); | |
| } | |
| } | |
| return undefined; | |
| } | |
| execStateFrameLocalCount(frame) { | |
| const scope_details = this.getLocalScopeDetails(frame); | |
| return scope_details ? scope_details.length : 0; | |
| } | |
| execStateFrameLocalName(frame, index) { | |
| const scope_details = this.getLocalScopeDetails(frame); | |
| if (index < 0 || index >= scope_details.length) return undefined; | |
| return scope_details[index].name; | |
| } | |
| execStateFrameLocalValue(frame, index) { | |
| const scope_details = this.getLocalScopeDetails(frame); | |
| if (index < 0 || index >= scope_details.length) return undefined; | |
| const local = scope_details[index]; | |
| let localValue; | |
| switch (local.value.type) { | |
| case "undefined": localValue = undefined; break; | |
| default: localValue = local.value.value; break; | |
| } | |
| return { value : () => localValue }; | |
| } | |
| reconstructValue(objectId) { | |
| const {msgid, msg} = this.createMessage( | |
| "Runtime.getProperties", { objectId : objectId, ownProperties: true }); | |
| this.sendMessage(msg); | |
| const reply = this.takeReplyChecked(msgid); | |
| return Object(reply.result.internalProperties[0].value.value); | |
| } | |
| reconstructRemoteObject(obj) { | |
| let value = obj.value; | |
| let isUndefined = false; | |
| switch (obj.type) { | |
| case "object": { | |
| switch (obj.subtype) { | |
| case "error": { | |
| const desc = obj.description; | |
| switch (obj.className) { | |
| case "EvalError": throw new EvalError(desc); | |
| case "RangeError": throw new RangeError(desc); | |
| case "ReferenceError": throw new ReferenceError(desc); | |
| case "SyntaxError": throw new SyntaxError(desc); | |
| case "TypeError": throw new TypeError(desc); | |
| case "URIError": throw new URIError(desc); | |
| default: throw new Error(desc); | |
| } | |
| break; | |
| } | |
| case "array": { | |
| const array = []; | |
| const props = this.propertiesToObject( | |
| this.getProperties(obj.objectId)); | |
| for (let i = 0; i < props.length; i++) { | |
| array[i] = props[i]; | |
| } | |
| value = array; | |
| break; | |
| } | |
| case "null": { | |
| value = null; | |
| break; | |
| } | |
| default: { | |
| switch (obj.className) { | |
| case "global": | |
| value = Function('return this')(); | |
| break; | |
| case "Number": | |
| case "String": | |
| case "Boolean": | |
| value = this.reconstructValue(obj.objectId); | |
| break; | |
| default: | |
| value = this.propertiesToObject( | |
| this.getProperties(obj.objectId)); | |
| break; | |
| } | |
| break; | |
| } | |
| } | |
| break; | |
| } | |
| case "undefined": { | |
| value = undefined; | |
| isUndefined = true; | |
| break; | |
| } | |
| case "number": { | |
| if (obj.description === "NaN") { | |
| value = NaN; | |
| } | |
| break; | |
| } | |
| case "string": | |
| case "boolean": { | |
| break; | |
| } | |
| default: { | |
| break; | |
| } | |
| } | |
| return { value : () => value, | |
| isUndefined : () => isUndefined, | |
| type : () => obj.type, | |
| className : () => obj.className | |
| }; | |
| } | |
| evaluateOnCallFrame(frame, expr, throw_on_side_effect = false) { | |
| const frameid = frame.callFrameId; | |
| const {msgid, msg} = this.createMessage( | |
| "Debugger.evaluateOnCallFrame", | |
| { callFrameId : frameid, | |
| expression : expr, | |
| throwOnSideEffect : throw_on_side_effect, | |
| }); | |
| this.sendMessage(msg); | |
| const reply = this.takeReplyChecked(msgid); | |
| const result = reply.result.result; | |
| return this.reconstructRemoteObject(result); | |
| } | |
| frameReceiver(frame) { | |
| return this.reconstructRemoteObject(frame.this); | |
| } | |
| frameReturnValue(frame) { | |
| return this.reconstructRemoteObject(frame.returnValue); | |
| } | |
| execStateFrameRestart(frame) { | |
| const frameid = frame.callFrameId; | |
| const {msgid, msg} = this.createMessage( | |
| "Debugger.restartFrame", { callFrameId : frameid }); | |
| this.sendMessage(msg); | |
| this.takeReplyChecked(msgid); | |
| } | |
| execStateFrame(frame) { | |
| const scriptid = parseInt(frame.location.scriptId); | |
| const line = frame.location.lineNumber; | |
| const column = frame.location.columnNumber; | |
| const loc = %ScriptLocationFromLine2(scriptid, line, column, 0); | |
| const func = { name : () => frame.functionName }; | |
| const index = JSON.parse(frame.callFrameId).ordinal; | |
| function allScopes() { | |
| const scopes = []; | |
| for (let i = 0; i < frame.scopeChain.length; i++) { | |
| scopes.push(this.execStateScope(frame, i)); | |
| } | |
| return scopes; | |
| } | |
| return { sourceColumn : () => column, | |
| sourceLine : () => line + 1, | |
| sourceLineText : () => loc.sourceText, | |
| sourcePosition : () => loc.position, | |
| evaluate : (expr, throw_on_side_effect) => | |
| this.evaluateOnCallFrame(frame, expr, throw_on_side_effect), | |
| functionName : () => frame.functionName, | |
| func : () => func, | |
| index : () => index, | |
| localCount : () => this.execStateFrameLocalCount(frame), | |
| localName : (ix) => this.execStateFrameLocalName(frame, ix), | |
| localValue: (ix) => this.execStateFrameLocalValue(frame, ix), | |
| receiver : () => this.frameReceiver(frame), | |
| restart : () => this.execStateFrameRestart(frame), | |
| returnValue : () => this.frameReturnValue(frame), | |
| scopeCount : () => frame.scopeChain.length, | |
| scope : (index) => this.execStateScope(frame, index), | |
| allScopes : allScopes.bind(this) | |
| }; | |
| } | |
| execStateEvaluateGlobal(expr) { | |
| const {msgid, msg} = this.createMessage( | |
| "Runtime.evaluate", { expression : expr }); | |
| this.sendMessage(msg); | |
| const reply = this.takeReplyChecked(msgid); | |
| const result = reply.result.result; | |
| return this.reconstructRemoteObject(result); | |
| } | |
| eventDataException(params) { | |
| switch (params.data.type) { | |
| case "string": { | |
| return params.data.value; | |
| } | |
| case "object": { | |
| const props = this.getProperties(params.data.objectId); | |
| return this.propertiesToObject(props); | |
| } | |
| default: { | |
| return undefined; | |
| } | |
| } | |
| } | |
| eventDataScriptSource(id) { | |
| const {msgid, msg} = this.createMessage( | |
| "Debugger.getScriptSource", { scriptId : String(id) }); | |
| this.sendMessage(msg); | |
| const reply = this.takeReplyChecked(msgid); | |
| return reply.result.scriptSource; | |
| } | |
| eventDataScriptSetSource(id, src) { | |
| const {msgid, msg} = this.createMessage( | |
| "Debugger.setScriptSource", { scriptId : id, scriptSource : src }); | |
| this.sendMessage(msg); | |
| this.takeReplyChecked(msgid); | |
| } | |
| eventDataScript(params) { | |
| const id = parseInt(params.scriptId); | |
| const name = params.url ? params.url : undefined; | |
| return { id : () => id, | |
| name : () => name, | |
| source : () => this.eventDataScriptSource(params.scriptId), | |
| setSource : (src) => this.eventDataScriptSetSource(id, src) | |
| }; | |
| } | |
| // --- Message handlers. ----------------------------------------------------- | |
| dispatchMessage(message) { | |
| const method = message.method; | |
| if (method == "Debugger.paused") { | |
| this.handleDebuggerPaused(message); | |
| } else if (method == "Debugger.scriptParsed") { | |
| this.handleDebuggerScriptParsed(message); | |
| } else if (method == "Debugger.scriptFailedToParse") { | |
| this.handleDebuggerScriptFailedToParse(message); | |
| } | |
| } | |
| handleDebuggerPaused(message) { | |
| const params = message.params; | |
| var debugEvent; | |
| switch (params.reason) { | |
| case "exception": | |
| case "promiseRejection": | |
| debugEvent = this.DebugEvent.Exception; | |
| break; | |
| case "OOM": | |
| debugEvent = this.DebugEvent.OOM; | |
| break; | |
| case "other": | |
| debugEvent = this.DebugEvent.Break; | |
| break; | |
| case "ambiguous": | |
| case "XHR": | |
| case "DOM": | |
| case "EventListener": | |
| case "assert": | |
| case "debugCommand": | |
| assertUnreachable(); | |
| default: | |
| assertUnreachable(); | |
| } | |
| if (!params.callFrames[0]) return; | |
| // Skip break events in this file. | |
| if (params.callFrames[0].location.scriptId == this.thisScriptId) return; | |
| // TODO(jgruber): Arguments as needed. | |
| let execState = { frames : params.callFrames, | |
| prepareStep : this.execStatePrepareStep.bind(this), | |
| evaluateGlobal : | |
| (expr) => this.execStateEvaluateGlobal(expr), | |
| frame : (index) => this.execStateFrame( | |
| index ? params.callFrames[index] | |
| : params.callFrames[0]), | |
| frameCount : () => params.callFrames.length | |
| }; | |
| let eventData = this.execStateFrame(params.callFrames[0]); | |
| if (debugEvent == this.DebugEvent.Exception) { | |
| eventData.uncaught = () => params.data.uncaught; | |
| eventData.exception = () => this.eventDataException(params); | |
| } | |
| this.invokeListener(debugEvent, execState, eventData); | |
| } | |
| handleDebuggerScriptParsed(message) { | |
| const params = message.params; | |
| let eventData = { scriptId : params.scriptId, | |
| script : () => this.eventDataScript(params), | |
| eventType : this.DebugEvent.AfterCompile | |
| } | |
| // TODO(jgruber): Arguments as needed. Still completely missing exec_state, | |
| // and eventData used to contain the script mirror instead of its id. | |
| this.invokeListener(this.DebugEvent.AfterCompile, undefined, eventData, | |
| undefined); | |
| } | |
| handleDebuggerScriptFailedToParse(message) { | |
| const params = message.params; | |
| let eventData = { scriptId : params.scriptId, | |
| script : () => this.eventDataScript(params), | |
| eventType : this.DebugEvent.CompileError | |
| } | |
| // TODO(jgruber): Arguments as needed. Still completely missing exec_state, | |
| // and eventData used to contain the script mirror instead of its id. | |
| this.invokeListener(this.DebugEvent.CompileError, undefined, eventData, | |
| undefined); | |
| } | |
| invokeListener(event, exec_state, event_data, data) { | |
| if (this.listener) { | |
| this.listener(event, exec_state, event_data, data); | |
| } | |
| } | |
| } | |
| // Simulate the debug object generated by --expose-debug-as debug. | |
| var debug = { instance : undefined }; | |
| Object.defineProperty(debug, 'Debug', { get: function() { | |
| if (!debug.instance) { | |
| debug.instance = new DebugWrapper(); | |
| debug.instance.enable(); | |
| } | |
| return debug.instance; | |
| }}); | |
| Object.defineProperty(debug, 'ScopeType', { get: function() { | |
| const instance = debug.Debug; | |
| return instance.ScopeType; | |
| }}); |