| OLD | NEW |
| 1 // Copyright 2016 the V8 project authors. All rights reserved. | 1 // Copyright 2016 the V8 project authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 "use strict"; | 5 "use strict"; |
| 6 | 6 |
| 7 // If true, prints all messages sent and received by inspector. | 7 // If true, prints all messages sent and received by inspector. |
| 8 const printProtocolMessages = false; | 8 const printProtocolMessages = false; |
| 9 | 9 |
| 10 // The active wrapper instance. | 10 // The active wrapper instance. |
| 11 let activeWrapper = undefined; | 11 let activeWrapper = undefined; |
| 12 | 12 |
| 13 // Receiver function called by inspector, delegating to active wrapper. | 13 // Receiver function called by inspector, delegating to active wrapper. |
| 14 function receive(message) { | 14 function receive(message) { |
| 15 activeWrapper.receiveMessage(message); | 15 activeWrapper.receiveMessage(message); |
| 16 } | 16 } |
| 17 | 17 |
| 18 class DebugWrapper { | 18 class DebugWrapper { |
| 19 constructor() { | 19 constructor() { |
| 20 // Message dictionary storing {id, message} pairs. | 20 // Message dictionary storing {id, message} pairs. |
| 21 this.receivedMessages = {}; | 21 this.receivedMessages = new Map(); |
| 22 | 22 |
| 23 // Each message dispatched by the Debug wrapper is assigned a unique number | 23 // Each message dispatched by the Debug wrapper is assigned a unique number |
| 24 // using nextMessageId. | 24 // using nextMessageId. |
| 25 this.nextMessageId = 0; | 25 this.nextMessageId = 0; |
| 26 | 26 |
| 27 // The listener method called on certain events. | 27 // The listener method called on certain events. |
| 28 this.listener = undefined; | 28 this.listener = undefined; |
| 29 | 29 |
| 30 // TODO(jgruber): Determine which of these are still required and possible. | 30 // TODO(jgruber): Determine which of these are still required and possible. |
| 31 // Debug events which can occur in the V8 JavaScript engine. | 31 // Debug events which can occur in the V8 JavaScript engine. |
| 32 this.DebugEvent = { Break: 1 | 32 this.DebugEvent = { Break: 1, |
| 33 , Exception: 2 | 33 Exception: 2, |
| 34 , NewFunction: 3 | 34 NewFunction: 3, |
| 35 , BeforeCompile: 4 | 35 BeforeCompile: 4, |
| 36 , AfterCompile: 5 | 36 AfterCompile: 5, |
| 37 , CompileError: 6 | 37 CompileError: 6, |
| 38 , AsyncTaskEvent: 7 | 38 AsyncTaskEvent: 7 |
| 39 }; | 39 }; |
| 40 | 40 |
| 41 // The different types of steps. |
| 42 this.StepAction = { StepOut: 0, |
| 43 StepNext: 1, |
| 44 StepIn: 2, |
| 45 StepFrame: 3, |
| 46 }; |
| 47 |
| 48 // A copy of the scope types from runtime-debug.cc. |
| 49 // NOTE: these constants should be backward-compatible, so |
| 50 // add new ones to the end of this list. |
| 51 this.ScopeType = { Global: 0, |
| 52 Local: 1, |
| 53 With: 2, |
| 54 Closure: 3, |
| 55 Catch: 4, |
| 56 Block: 5, |
| 57 Script: 6, |
| 58 Eval: 7, |
| 59 Module: 8 |
| 60 }; |
| 61 |
| 62 // Store the current script id so we can skip corresponding break events. |
| 63 this.thisScriptId = %FunctionGetScriptId(receive); |
| 64 |
| 41 // Register as the active wrapper. | 65 // Register as the active wrapper. |
| 42 assertTrue(activeWrapper === undefined); | 66 assertTrue(activeWrapper === undefined); |
| 43 activeWrapper = this; | 67 activeWrapper = this; |
| 44 } | 68 } |
| 45 | 69 |
| 46 enable() { this.sendMessageForMethodChecked("Debugger.enable"); } | 70 enable() { this.sendMessageForMethodChecked("Debugger.enable"); } |
| 47 disable() { this.sendMessageForMethodChecked("Debugger.disable"); } | 71 disable() { this.sendMessageForMethodChecked("Debugger.disable"); } |
| 48 | 72 |
| 49 setListener(listener) { this.listener = listener; } | 73 setListener(listener) { this.listener = listener; } |
| 50 | 74 |
| (...skipping 11 matching lines...) Expand all Loading... |
| 62 | 86 |
| 63 const scriptid = %FunctionGetScriptId(func); | 87 const scriptid = %FunctionGetScriptId(func); |
| 64 assertTrue(scriptid != -1); | 88 assertTrue(scriptid != -1); |
| 65 | 89 |
| 66 const offset = %FunctionGetScriptSourcePosition(func); | 90 const offset = %FunctionGetScriptSourcePosition(func); |
| 67 const loc = | 91 const loc = |
| 68 %ScriptLocationFromLine2(scriptid, opt_line, opt_column, offset); | 92 %ScriptLocationFromLine2(scriptid, opt_line, opt_column, offset); |
| 69 | 93 |
| 70 const {msgid, msg} = this.createMessage( | 94 const {msgid, msg} = this.createMessage( |
| 71 "Debugger.setBreakpoint", | 95 "Debugger.setBreakpoint", |
| 72 { location : { scriptId : scriptid.toString() | 96 { location : { scriptId : scriptid.toString(), |
| 73 , lineNumber : loc.line | 97 lineNumber : loc.line, |
| 74 , columnNumber : loc.column | 98 columnNumber : loc.column |
| 75 } | 99 } |
| 76 }); | 100 }); |
| 77 this.sendMessage(msg); | 101 this.sendMessage(msg); |
| 78 | 102 |
| 79 const reply = this.receivedMessages[msgid]; | 103 const reply = this.takeReplyChecked(msgid); |
| 104 assertTrue(reply.result !== undefined); |
| 80 const breakid = reply.result.breakpointId; | 105 const breakid = reply.result.breakpointId; |
| 81 assertTrue(breakid !== undefined); | 106 assertTrue(breakid !== undefined); |
| 82 | 107 |
| 83 return breakid; | 108 return breakid; |
| 84 } | 109 } |
| 85 | 110 |
| 86 clearBreakPoint(breakid) { | 111 clearBreakPoint(breakid) { |
| 87 const {msgid, msg} = this.createMessage( | 112 const {msgid, msg} = this.createMessage( |
| 88 "Debugger.removeBreakpoint", { breakpointId : breakid }); | 113 "Debugger.removeBreakpoint", { breakpointId : breakid }); |
| 89 this.sendMessage(msg); | 114 this.sendMessage(msg); |
| 90 assertTrue(this.receivedMessages[msgid] !== undefined); | 115 this.takeReplyChecked(msgid); |
| 91 } | 116 } |
| 92 | 117 |
| 93 // Returns the serialized result of the given expression. For example: | 118 // Returns the serialized result of the given expression. For example: |
| 94 // {"type":"number", "value":33, "description":"33"}. | 119 // {"type":"number", "value":33, "description":"33"}. |
| 95 evaluate(frameid, expression) { | 120 evaluate(frameid, expression) { |
| 96 const {msgid, msg} = this.createMessage( | 121 const {msgid, msg} = this.createMessage( |
| 97 "Debugger.evaluateOnCallFrame", | 122 "Debugger.evaluateOnCallFrame", |
| 98 { callFrameId : frameid | 123 { callFrameId : frameid, |
| 99 , expression : expression | 124 expression : expression |
| 100 }); | 125 }); |
| 101 this.sendMessage(msg); | 126 this.sendMessage(msg); |
| 102 | 127 |
| 103 const reply = this.receivedMessages[msgid]; | 128 const reply = this.takeReplyChecked(msgid); |
| 104 return reply.result.result; | 129 return reply.result.result; |
| 105 } | 130 } |
| 106 | 131 |
| 107 // --- Internal methods. ----------------------------------------------------- | 132 // --- Internal methods. ----------------------------------------------------- |
| 108 | 133 |
| 109 getNextMessageId() { | 134 getNextMessageId() { |
| 110 return this.nextMessageId++; | 135 return this.nextMessageId++; |
| 111 } | 136 } |
| 112 | 137 |
| 113 createMessage(method, params) { | 138 createMessage(method, params) { |
| 114 const id = this.getNextMessageId(); | 139 const id = this.getNextMessageId(); |
| 115 const msg = JSON.stringify({ | 140 const msg = JSON.stringify({ |
| 116 id: id, | 141 id: id, |
| 117 method: method, | 142 method: method, |
| 118 params: params, | 143 params: params, |
| 119 }); | 144 }); |
| 120 return {msgid: id, msg: msg}; | 145 return { msgid : id, msg: msg }; |
| 121 } | 146 } |
| 122 | 147 |
| 123 receiveMessage(message) { | 148 receiveMessage(message) { |
| 124 if (printProtocolMessages) print(message); | 149 if (printProtocolMessages) print(message); |
| 125 | 150 |
| 126 const parsedMessage = JSON.parse(message); | 151 const parsedMessage = JSON.parse(message); |
| 127 if (parsedMessage.id !== undefined) { | 152 if (parsedMessage.id !== undefined) { |
| 128 this.receivedMessages[parsedMessage.id] = parsedMessage; | 153 this.receivedMessages.set(parsedMessage.id, parsedMessage); |
| 129 } | 154 } |
| 130 | 155 |
| 131 this.dispatchMessage(parsedMessage); | 156 this.dispatchMessage(parsedMessage); |
| 132 } | 157 } |
| 133 | 158 |
| 134 sendMessage(message) { | 159 sendMessage(message) { |
| 135 if (printProtocolMessages) print(message); | 160 if (printProtocolMessages) print(message); |
| 136 send(message); | 161 send(message); |
| 137 } | 162 } |
| 138 | 163 |
| 139 sendMessageForMethodChecked(method) { | 164 sendMessageForMethodChecked(method) { |
| 140 const {msgid, msg} = this.createMessage(method); | 165 const {msgid, msg} = this.createMessage(method); |
| 141 this.sendMessage(msg); | 166 this.sendMessage(msg); |
| 142 assertTrue(this.receivedMessages[msgid] !== undefined); | 167 this.takeReplyChecked(msgid); |
| 168 } |
| 169 |
| 170 takeReplyChecked(msgid) { |
| 171 const reply = this.receivedMessages.get(msgid); |
| 172 assertTrue(reply !== undefined); |
| 173 this.receivedMessages.delete(msgid); |
| 174 return reply; |
| 175 } |
| 176 |
| 177 execStatePrepareStep(action) { |
| 178 switch(action) { |
| 179 case this.StepAction.StepOut: this.stepOut(); break; |
| 180 case this.StepAction.StepNext: this.stepOver(); break; |
| 181 case this.StepAction.StepIn: this.stepInto(); break; |
| 182 default: %AbortJS("Unsupported StepAction"); break; |
| 183 } |
| 184 } |
| 185 |
| 186 execStateScope(scope) { |
| 187 // TODO(jgruber): Mapping |
| 188 return { scopeType: () => scope.type, |
| 189 scopeObject: () => scope.object |
| 190 }; |
| 191 } |
| 192 |
| 193 execStateFrame(frame) { |
| 194 const scriptid = parseInt(frame.location.scriptId); |
| 195 const line = frame.location.lineNumber; |
| 196 const column = frame.location.columnNumber; |
| 197 const loc = %ScriptLocationFromLine2(scriptid, line, column, 0); |
| 198 const func = { name : () => frame.functionName }; |
| 199 return { sourceLineText : () => loc.sourceText, |
| 200 functionName : () => frame.functionName, |
| 201 func : () => func, |
| 202 scopeCount : () => frame.scopeChain.length, |
| 203 scope : (index) => this.execStateScope(frame.scopeChain[index]) |
| 204 }; |
| 143 } | 205 } |
| 144 | 206 |
| 145 // --- Message handlers. ----------------------------------------------------- | 207 // --- Message handlers. ----------------------------------------------------- |
| 146 | 208 |
| 147 dispatchMessage(message) { | 209 dispatchMessage(message) { |
| 148 const method = message.method; | 210 const method = message.method; |
| 149 if (method == "Debugger.paused") { | 211 if (method == "Debugger.paused") { |
| 150 this.handleDebuggerPaused(message); | 212 this.handleDebuggerPaused(message); |
| 151 } else if (method == "Debugger.scriptParsed") { | 213 } else if (method == "Debugger.scriptParsed") { |
| 152 this.handleDebuggerScriptParsed(message); | 214 this.handleDebuggerScriptParsed(message); |
| 153 } | 215 } |
| 154 } | 216 } |
| 155 | 217 |
| 156 handleDebuggerPaused(message) { | 218 handleDebuggerPaused(message) { |
| 157 const params = message.params; | 219 const params = message.params; |
| 158 | 220 |
| 221 // Skip break events in this file. |
| 222 if (params.callFrames[0].location.scriptId == this.thisScriptId) return; |
| 223 |
| 159 // TODO(jgruber): Arguments as needed. | 224 // TODO(jgruber): Arguments as needed. |
| 160 let execState = { frames: params.callFrames }; | 225 let execState = { frames : params.callFrames, |
| 161 this.invokeListener(this.DebugEvent.Break, execState); | 226 prepareStep : this.execStatePrepareStep.bind(this), |
| 227 frame : (index) => this.execStateFrame( |
| 228 index ? params.callFrames[index] |
| 229 : params.callFrames[0]), |
| 230 frameCount : () => params.callFrames.length |
| 231 }; |
| 232 let eventData = this.execStateFrame(params.callFrames[0]); |
| 233 this.invokeListener(this.DebugEvent.Break, execState, eventData); |
| 162 } | 234 } |
| 163 | 235 |
| 164 handleDebuggerScriptParsed(message) { | 236 handleDebuggerScriptParsed(message) { |
| 165 const params = message.params; | 237 const params = message.params; |
| 166 let eventData = { scriptId : params.scriptId | 238 let eventData = { scriptId : params.scriptId, |
| 167 , eventType : this.DebugEvent.AfterCompile | 239 eventType : this.DebugEvent.AfterCompile |
| 168 } | 240 } |
| 169 | 241 |
| 170 // TODO(jgruber): Arguments as needed. Still completely missing exec_state, | 242 // TODO(jgruber): Arguments as needed. Still completely missing exec_state, |
| 171 // and eventData used to contain the script mirror instead of its id. | 243 // and eventData used to contain the script mirror instead of its id. |
| 172 this.invokeListener(this.DebugEvent.AfterCompile, undefined, eventData, | 244 this.invokeListener(this.DebugEvent.AfterCompile, undefined, eventData, |
| 173 undefined); | 245 undefined); |
| 174 } | 246 } |
| 175 | 247 |
| 176 invokeListener(event, exec_state, event_data, data) { | 248 invokeListener(event, exec_state, event_data, data) { |
| 177 if (this.listener) { | 249 if (this.listener) { |
| 178 this.listener(event, exec_state, event_data, data); | 250 this.listener(event, exec_state, event_data, data); |
| 179 } | 251 } |
| 180 } | 252 } |
| 181 } | 253 } |
| 254 |
| 255 // Simulate the debug object generated by --expose-debug-as debug. |
| 256 var debug = { instance : undefined }; |
| 257 Object.defineProperty(debug, 'Debug', { get: function() { |
| 258 if (!debug.instance) { |
| 259 debug.instance = new DebugWrapper(); |
| 260 debug.instance.enable(); |
| 261 } |
| 262 return debug.instance; |
| 263 }}); |
| OLD | NEW |