| // 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; |
| }}); |