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 |