OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 /** | |
6 * @fileoverview Shell objects and global helper functions for Chrome | |
7 * automation shell / debugger. This file is loaded into the global namespace | |
8 * of the interactive shell, so users can simply call global functions | |
9 * directly. | |
10 */ | |
11 | |
12 // TODO(erikkay): look into how this can be split up into multiple files | |
13 // It's currently loaded explicitly by Chrome, so maybe I need an "include" | |
14 // or "source" builtin to allow a core source file to reference multiple | |
15 // sub-files. | |
16 | |
17 /** | |
18 * Sequence number of the DebugCommand. | |
19 */ | |
20 DebugCommand.next_seq_ = 0; | |
21 | |
22 /** | |
23 * Command messages to be sent to the debugger. | |
24 * @constructor | |
25 */ | |
26 function DebugCommand(str) { | |
27 this.command = undefined; | |
28 // first, strip off of the leading word as the command | |
29 var argv = str.split(' '); | |
30 this.user_command = argv.shift(); | |
31 // the rest of the string is argv to the command | |
32 str = argv.join(' '); | |
33 if (DebugCommand.aliases[this.user_command]) | |
34 this.user_command = DebugCommand.aliases[this.user_command]; | |
35 if (this.parseArgs_(str) == 1) | |
36 this.type = "request"; | |
37 if (this.command == undefined) | |
38 this.command = this.user_command; | |
39 }; | |
40 | |
41 // Mapping of some control characters to avoid the \uXXXX syntax for most | |
42 // commonly used control cahracters. | |
43 const ctrlCharMap_ = { | |
44 '\b': '\\b', | |
45 '\t': '\\t', | |
46 '\n': '\\n', | |
47 '\f': '\\f', | |
48 '\r': '\\r', | |
49 '"' : '\\"', | |
50 '\\': '\\\\' | |
51 }; | |
52 | |
53 // Regular expression matching ", \ and control characters (0x00 - 0x1F) | |
54 // globally. | |
55 const ctrlCharMatch_ = /["\\\\\x00-\x1F]/g; | |
56 | |
57 /** | |
58 * Convert a String to its JSON representation. | |
59 * @param {String} value - String to be converted | |
60 * @return {String} JSON formatted String | |
61 */ | |
62 DebugCommand.stringToJSON = function(value) { | |
63 // Check for" , \ and control characters (0x00 - 0x1F). | |
64 if (ctrlCharMatch_.test(value)) { | |
65 // Replace ", \ and control characters (0x00 - 0x1F). | |
66 return '"' + value.replace(ctrlCharMatch_, function (char) { | |
67 // Use charmap if possible. | |
68 var mapped = ctrlCharMap_[char]; | |
69 if (mapped) return mapped; | |
70 mapped = char.charCodeAt(); | |
71 // Convert control character to unicode escape sequence. | |
72 var dig1 = (Math.floor(mapped / 16)); | |
73 var dig2 = (mapped % 16) | |
74 return '\\u00' + dig1.toString(16) + dig2.toString(16); | |
75 }) | |
76 + '"'; | |
77 } | |
78 | |
79 // Simple string with no special characters. | |
80 return '"' + value + '"'; | |
81 }; | |
82 | |
83 /** | |
84 * @return {bool} True if x is an integer. | |
85 */ | |
86 DebugCommand.isInt = function(x) { | |
87 var y = parseInt(x); | |
88 if (isNaN(y)) | |
89 return false; | |
90 return x == y && x.toString() == y.toString(); | |
91 }; | |
92 | |
93 /** | |
94 * @return {float} log base 10 of num | |
95 */ | |
96 DebugCommand.log10 = function(num) { | |
97 return Math.log(num)/Math.log(10); | |
98 }; | |
99 | |
100 /** | |
101 * Take an object and encode it (non-recursively) as a JSON dict. | |
102 * @param {Object} obj - object to encode | |
103 */ | |
104 DebugCommand.toJSON = function(obj) { | |
105 // TODO(erikkay): use a real JSON library | |
106 var json = '{'; | |
107 for (var key in obj) { | |
108 if (json.length > 1) | |
109 json += ","; | |
110 var val = obj[key]; | |
111 if (!DebugCommand.isInt(val)) { | |
112 val = DebugCommand.stringToJSON(val.toString()); | |
113 } | |
114 json += '"' + key + '":' + val; | |
115 } | |
116 json += '}'; | |
117 return json; | |
118 }; | |
119 | |
120 /** | |
121 * Encode the DebugCommand object into the V8 debugger JSON protocol format. | |
122 */ | |
123 DebugCommand.prototype.toJSONProtocol = function() { | |
124 // TODO(erikkay): use a real JSON library | |
125 var json = '{'; | |
126 json += '"seq":"' + this.seq; | |
127 json += '","type":"' + this.type; | |
128 json += '","command":"' + this.command + '"'; | |
129 if (this.arguments) { | |
130 json += ',"arguments":' + DebugCommand.toJSON(this.arguments); | |
131 } | |
132 json += '}' | |
133 return json; | |
134 } | |
135 | |
136 /** | |
137 * Encode the contents of this message and send it to the debugger. | |
138 * @param {Object} tab - tab being debugged. This is an internal | |
139 * Chrome object. | |
140 */ | |
141 DebugCommand.prototype.sendToDebugger = function(tab) { | |
142 this.seq = DebugCommand.next_seq_++; | |
143 str = this.toJSONProtocol(); | |
144 dprint("sending: " + str); | |
145 tab.sendToDebugger(str); | |
146 }; | |
147 | |
148 DebugCommand.trim = function(str) { | |
149 return str.replace(/^\s*/, '').replace(/\s*$/, ''); | |
150 }; | |
151 | |
152 /** | |
153 * Strip off a trailing parameter after a ':'. As the identifier for the | |
154 * source can contain ':' characters (e.g. 'http://www....) something after | |
155 * a ':' is only considered a parameter if it is numeric. | |
156 * @return {Array} two element array, the trimmed string and the parameter, | |
157 * or -1 if no parameter | |
158 */ | |
159 DebugCommand.stripTrailingParameter = function(str, opt_separator) { | |
160 var sep = opt_separator || ':'; | |
161 var index = str.lastIndexOf(sep); | |
162 // If a separator character if found strip if numeric. | |
163 if (index != -1) { | |
164 var value = parseInt(str.substring(index + 1, str.length), 10); | |
165 if (isNaN(value) || value < 0) { | |
166 return [str, -1]; | |
167 } | |
168 str = str.substring(0, index); | |
169 return [str, value]; | |
170 } | |
171 return [str, -1]; | |
172 }; | |
173 | |
174 /** | |
175 * Format source and location strings based on source location input data. | |
176 * @param {Object} script - script information object | |
177 * @param {String} source - source code for the current location | |
178 * @param {int} line - line number (0-based) | |
179 * @param {String} func - function name | |
180 * @return {array} [location(string), source line(string), line number(int)] | |
181 */ | |
182 DebugCommand.getSourceLocation = function(script, source, line, func) { | |
183 // source line is 0-based, we present as 1-based | |
184 line++; | |
185 | |
186 // TODO(erikkay): take column into account as well | |
187 if (source) | |
188 source = "" + line + ": " + source; | |
189 var location = ''; | |
190 if (func) { | |
191 location = func + ", "; | |
192 } | |
193 location += script ? script.name : '[no source]'; | |
194 return [location, source, line]; | |
195 }; | |
196 | |
197 /** | |
198 * Aliases for debugger commands. | |
199 */ | |
200 DebugCommand.aliases = { | |
201 'b': 'break', | |
202 'bi': 'break_info', | |
203 'br': 'break', | |
204 'bt': 'backtrace', | |
205 'c': 'continue', | |
206 'f': 'frame', | |
207 'h': 'help', | |
208 '?': 'help', | |
209 'ls': 'source', | |
210 'n': 'next', | |
211 'p': 'print', | |
212 's': 'step', | |
213 'so': 'stepout', | |
214 }; | |
215 | |
216 /** | |
217 * Parses arguments to "args" and "locals" command, and initializes | |
218 * the underlying DebugCommand (which is a frame request). | |
219 * @see DebugCommand.commands | |
220 * @param {string} str The arguments to be parsed. | |
221 * @return -1 for usage error, 1 for success | |
222 */ | |
223 DebugCommand.prototype.parseArgsAndLocals_ = function(str) { | |
224 this.command = "frame"; | |
225 return str.length ? -1 : 1; | |
226 }; | |
227 | |
228 /** | |
229 * Parses arguments to "break_info" command, and executes it. | |
230 * "break_info" has an optional argument, which is the breakpoint | |
231 * identifier. | |
232 * @see DebugCommand.commands | |
233 * @param {string} str - The arguments to be parsed. | |
234 * @return -1 for usage error, 0 for success | |
235 */ | |
236 DebugCommand.prototype.parseBreakInfo_ = function(str) { | |
237 this.type = "shell"; | |
238 | |
239 // Array of breakpoints to be printed by this command | |
240 // (default to all breakpoints) | |
241 var breakpointsToPrint = shell_.breakpoints; | |
242 | |
243 if (str.length > 0) { | |
244 // User specified an invalid breakpoint (not a number) | |
245 if (!str.match(/^\s*\d+\s*$/)) | |
246 return -1; // invalid usage | |
247 | |
248 // Check that the specified breakpoint identifier exists | |
249 var id = parseInt(str); | |
250 var info = shell_.breakpoints[id]; | |
251 if (!info) { | |
252 print("Error: Invalid breakpoint"); | |
253 return 0; // success (of sorts) | |
254 } | |
255 breakpointsToPrint = [info]; | |
256 } else { | |
257 // breakpointsToPrint.length isn't accurate, because of | |
258 // deletions | |
259 var num_breakpoints = 0; | |
260 for (var i in breakpointsToPrint) num_breakpoints++; | |
261 | |
262 print("Num breakpoints: " + num_breakpoints); | |
263 } | |
264 | |
265 DebugShell.printBreakpoints_(breakpointsToPrint); | |
266 | |
267 return 0; // success | |
268 } | |
269 | |
270 /** | |
271 * Parses arguments to "step" command. | |
272 * @see DebugCommand.commands | |
273 * @param {string} str The arguments to be parsed. | |
274 * @return -1 for usage error, 1 for success | |
275 */ | |
276 DebugCommand.prototype.parseStep_ = function(str, opt_stepaction) { | |
277 this.command = "continue"; | |
278 action = opt_stepaction || "in"; | |
279 this.arguments = {"stepaction" : action} | |
280 if (str.length) { | |
281 count = parseInt(str); | |
282 if (count > 0) { | |
283 this.arguments["stepcount"] = count; | |
284 } else { | |
285 return -1; | |
286 } | |
287 } | |
288 return 1; | |
289 }; | |
290 | |
291 /** | |
292 * Parses arguments to "step" command. | |
293 * @see DebugCommand.commands | |
294 * @param {string} str The arguments to be parsed. | |
295 * @return -1 for usage error, 1 for success | |
296 */ | |
297 DebugCommand.prototype.parseStepOut_ = function(str) { | |
298 return this.parseStep_(str, "out"); | |
299 }; | |
300 | |
301 /** | |
302 * Parses arguments to "next" command. | |
303 * @see DebugCommand.commands | |
304 * @param {string} str The arguments to be parsed. | |
305 * @return -1 for usage error, 1 for success | |
306 */ | |
307 DebugCommand.prototype.parseNext_ = function(str) { | |
308 return this.parseStep_(str, "next"); | |
309 }; | |
310 | |
311 /** | |
312 * Parse the arguments to "print" command. | |
313 * @see DebugCommand.commands | |
314 * @param {string} str The arguments to be parsed. | |
315 * @return 1 - always succeeds | |
316 */ | |
317 DebugCommand.prototype.parsePrint_ = function(str) { | |
318 this.command = "evaluate"; | |
319 this.arguments = { "expression" : str }; | |
320 // If the page is in the running state, then we force the expression to | |
321 // evaluate in the global context to avoid evaluating in a random context. | |
322 if (shell_.running) | |
323 this.arguments["global"] = true; | |
324 return 1; | |
325 }; | |
326 | |
327 /** | |
328 * Handle the response to a "print" command and display output to user. | |
329 * @param {ProtocolPacket} evaluate_response - the V8 debugger response object | |
330 */ | |
331 DebugCommand.responsePrint_ = function(evaluate_response) { | |
332 body = evaluate_response.body(); | |
333 if (body['text'] != undefined) { | |
334 print(body['text']); | |
335 } else { | |
336 // TODO(erikkay): is "text" ever not set? | |
337 print("can't print response"); | |
338 } | |
339 }; | |
340 | |
341 /** | |
342 * Parse the arguments to "dir" command. | |
343 * @see DebugCommand.commands | |
344 * @param {string} str The arguments to be parsed. | |
345 * @return 1 - always succeeds | |
346 */ | |
347 DebugCommand.prototype.parseDir_ = function(str) { | |
348 this.command = "evaluate"; | |
349 this.arguments = { "expression" : str }; | |
350 // If the page is in the running state, then we force the expression to | |
351 // evaluate in the global context to avoid evaluating in a random context. | |
352 if (shell_.running) | |
353 this.arguments["global"] = true; | |
354 return 1; | |
355 }; | |
356 | |
357 /** | |
358 * Handle the response to a "dir" command and display output to user. | |
359 * @see http://wiki/Main/V8Debugger | |
360 * @param {ProtocolPacket} evaluate_response - the V8 debugger response object | |
361 */ | |
362 DebugCommand.responseDir_ = function(evaluate_response) { | |
363 var body = evaluate_response.body(); | |
364 if (body.properties) { | |
365 print(body.properties.length + ' properties'); | |
366 for (var n in body.properties) { | |
367 var property_info = body.properties[n].name; | |
368 property_info += ': '; | |
369 var value = evaluate_response.lookup(body.properties[n].ref); | |
370 if (value && value.type) { | |
371 property_info += value.type; | |
372 } else { | |
373 property_info += '<no type>'; | |
374 } | |
375 property_info += ' (#'; | |
376 property_info += body.properties[n].ref; | |
377 property_info += '#)'; | |
378 print(property_info); | |
379 } | |
380 } | |
381 }; | |
382 | |
383 /** | |
384 * Parses arguments to "break" command. See DebugCommand.commands below | |
385 * for syntax details. | |
386 * @see DebugCommand.commands | |
387 * @param {string} str The arguments to be parsed. | |
388 * @return -1 for usage error, 1 for success, 0 for handled internally | |
389 */ | |
390 DebugCommand.prototype.parseBreak_ = function(str) { | |
391 function stripTrailingParameter() { | |
392 var ret = DebugCommand.stripTrailingParameter(str, ':'); | |
393 str = ret[0]; | |
394 return ret[1]; | |
395 } | |
396 | |
397 if (str.length == 0) { | |
398 this.command = "break"; | |
399 return 1; | |
400 } else { | |
401 var parts = str.split(/\s+/); | |
402 var condition = null; | |
403 if (parts.length > 1) { | |
404 str = parts.shift(); | |
405 condition = parts.join(" "); | |
406 } | |
407 | |
408 this.command = "setbreakpoint"; | |
409 | |
410 // Locate ...[:line[:column]] if present. | |
411 var line = -1; | |
412 var column = -1; | |
413 line = stripTrailingParameter(); | |
414 if (line != -1) { | |
415 line -= 1; | |
416 var l = stripTrailingParameter(); | |
417 if (l != -1) { | |
418 column = line; | |
419 line = l - 1; | |
420 } | |
421 } | |
422 | |
423 if (line == -1 && column == -1) { | |
424 this.arguments = { 'type' : 'function', | |
425 'target' : str }; | |
426 } else { | |
427 var script = shell_.matchScript(str, line); | |
428 if (script) { | |
429 this.arguments = { 'type' : 'script', | |
430 'target' : script.name }; | |
431 } else { | |
432 this.arguments = { 'type' : 'function', | |
433 'target' : str }; | |
434 } | |
435 this.arguments.line = line; | |
436 if (column != -1) | |
437 this.arguments.position = column; | |
438 } | |
439 if (condition) | |
440 this.arguments.condition = condition; | |
441 } | |
442 return 1; | |
443 }; | |
444 | |
445 /** | |
446 * Handle the response to a "break" command and display output to user. | |
447 * @param {ResponsePacket} setbreakpoint_response - the V8 debugger response | |
448 * object | |
449 */ | |
450 DebugCommand.responseBreak_ = function(setbreakpoint_response) { | |
451 var body = setbreakpoint_response.body(); | |
452 var info = new BreakpointInfo( | |
453 parseInt(body.breakpoint), | |
454 setbreakpoint_response.command.arguments.type, | |
455 setbreakpoint_response.command.arguments.target, | |
456 setbreakpoint_response.command.arguments.line, | |
457 setbreakpoint_response.command.arguments.position, | |
458 setbreakpoint_response.command.arguments.condition); | |
459 shell_.addedBreakpoint(info); | |
460 }; | |
461 | |
462 /** | |
463 * Parses arguments to "backtrace" command. See DebugCommand.commands below | |
464 * for syntax details. | |
465 * @see DebugCommand.commands | |
466 * @param {string} str The arguments to be parsed. | |
467 * @return -1 for usage error, 1 for success | |
468 */ | |
469 DebugCommand.prototype.parseBacktrace_ = function(str) { | |
470 if (str.length > 0) { | |
471 var parts = str.split(/\s+/); | |
472 var non_empty_parts = parts.filter(function(s) { return s.length > 0; }); | |
473 // We need exactly two arguments. | |
474 if (non_empty_parts.length != 2) { | |
475 return -1; | |
476 } | |
477 var from = parseInt(non_empty_parts[0], 10); | |
478 var to = parseInt(non_empty_parts[1], 10); | |
479 // The two arguments have to be integers. | |
480 if (from != non_empty_parts[0] || to != non_empty_parts[1]) { | |
481 return -1; | |
482 } | |
483 this.arguments = { 'fromFrame': from, 'toFrame': to + 1 }; | |
484 } else { | |
485 // Default to fetching the first 10 frames. | |
486 this.arguments = { 'fromFrame': 0, 'toFrame': 10 }; | |
487 } | |
488 return 1; | |
489 }; | |
490 | |
491 /** | |
492 * Handle the response to a "backtrace" command and display output to user. | |
493 * @param {ResponsePacket} backtrace_response - the V8 debugger response object | |
494 */ | |
495 DebugCommand.responseBacktrace_ = function(backtrace_response) { | |
496 body = backtrace_response.body(); | |
497 if (body && body.totalFrames) { | |
498 print('Frames #' + body.fromFrame + ' to #' + (body.toFrame - 1) + | |
499 ' of ' + body.totalFrames + ":"); | |
500 for (var i = 0; i < body.frames.length; i++) { | |
501 print(body.frames[i].text); | |
502 } | |
503 } else { | |
504 print("unimplemented (sorry)"); | |
505 } | |
506 }; | |
507 | |
508 | |
509 /** | |
510 * Parses arguments to "clear" command. See DebugCommand.commands below | |
511 * for syntax details. | |
512 * @see DebugCommand.commands | |
513 * @param {string} str The arguments to be parsed. | |
514 * @return -1 for usage error, 1 for success | |
515 */ | |
516 DebugCommand.prototype.parseClearCommand_ = function(str) { | |
517 this.command = "clearbreakpoint"; | |
518 if (str.length > 0) { | |
519 var i = parseInt(str, 10); | |
520 if (i != str) { | |
521 return -1; | |
522 } | |
523 this.arguments = { 'breakpoint': i }; | |
524 } | |
525 return 1; | |
526 } | |
527 | |
528 /** | |
529 * Handle the response to a "clear" command and display output to user. | |
530 * @param {ResponsePacket} clearbreakpoint_response - the V8 debugger response | |
531 * object | |
532 */ | |
533 DebugCommand.responseClear_ = function(clearbreakpoint_response) { | |
534 var body = clearbreakpoint_response.body(); | |
535 shell_.clearedBreakpoint(parseInt(msg.command.arguments.breakpoint)); | |
536 } | |
537 | |
538 | |
539 /** | |
540 * Parses arguments to "continue" command. See DebugCommand.commands below | |
541 * for syntax details. | |
542 * @see DebugCommand.commands | |
543 * @param {string} str The arguments to be parsed. | |
544 * @return -1 for usage error, 1 for success | |
545 */ | |
546 DebugCommand.prototype.parseContinueCommand_ = function(str) { | |
547 this.command = "continue"; | |
548 if (str.length > 0) { | |
549 return -1; | |
550 } | |
551 return 1; | |
552 } | |
553 | |
554 /** | |
555 * Parses arguments to "frame" command. See DebugCommand.commands below | |
556 * for syntax details. | |
557 * @see DebugCommand.commands | |
558 * @param {string} str The arguments to be parsed. | |
559 * @return -1 for usage error, 1 for success | |
560 */ | |
561 DebugCommand.prototype.parseFrame_ = function(str) { | |
562 if (str.length > 0) { | |
563 var i = parseInt(str, 10); | |
564 if (i != str) { | |
565 return -1; | |
566 } | |
567 this.arguments = { 'number': i }; | |
568 } | |
569 return 1; | |
570 }; | |
571 | |
572 /** | |
573 * Handle the response to a "frame" command and display output to user. | |
574 * @param {ResponsePacket} frame_response - the V8 debugger response object | |
575 */ | |
576 DebugCommand.responseFrame_ = function(frame_response) { | |
577 var body = frame_response.body(); | |
578 var func = frame_response.lookup(body.func.ref); | |
579 loc = DebugCommand.getSourceLocation(func.script, | |
580 body.sourceLineText, body.line, func.name); | |
581 print("#" + (body.index <= 9 ? '0' : '') + body.index + " " + loc[0]); | |
582 print(loc[1]); | |
583 shell_.current_frame = body.index; | |
584 shell_.current_line = loc[2]; | |
585 shell_.current_script = func.script; | |
586 }; | |
587 | |
588 /** | |
589 * Handle the response to a "args" command and display output to user. | |
590 * @param {ProtocolPacket} frame_response - the V8 debugger response object (for | |
591 * "frame" command) | |
592 */ | |
593 DebugCommand.responseArgs_ = function(frame_response) { | |
594 var body = frame_response.body(); | |
595 DebugCommand.printVariables_(body.arguments, frame_response); | |
596 } | |
597 | |
598 /** | |
599 * Handle the response to a "locals" command and display output to user. | |
600 * @param {Object} msg - the V8 debugger response object (for "frame" command) | |
601 */ | |
602 DebugCommand.responseLocals_ = function(frame_response) { | |
603 var body = frame_response.body(); | |
604 DebugCommand.printVariables_(body.locals, frame_response); | |
605 } | |
606 | |
607 DebugCommand.printVariables_ = function(variables, protocol_packet) { | |
608 for (var i = 0; i < variables.length; i++) { | |
609 print(variables[i].name + " = " + | |
610 DebugCommand.toPreviewString_(protocol_packet.lookup(variables[i].value.
ref))); | |
611 } | |
612 } | |
613 | |
614 DebugCommand.toPreviewString_ = function(value) { | |
615 // TODO(ericroman): pretty print arrays and objects, recursively. | |
616 // TODO(ericroman): truncate length of preview if too long? | |
617 if (value.type == "string") { | |
618 // Wrap the string in quote marks and JS-escape | |
619 return DebugCommand.stringToJSON(value.text); | |
620 } | |
621 return value.text; | |
622 } | |
623 | |
624 /** | |
625 * Parses arguments to "scripts" command. | |
626 * @see DebugCommand.commands | |
627 * @param {string} str - The arguments to be parsed. | |
628 * @return -1 for usage error, 1 for success | |
629 */ | |
630 DebugCommand.prototype.parseScripts_ = function(str) { | |
631 return 1 | |
632 }; | |
633 | |
634 /** | |
635 * Handle the response to a "scripts" command and display output to user. | |
636 * @param {ResponsePacket} scripts_response - the V8 debugger response object | |
637 */ | |
638 DebugCommand.responseScripts_ = function(scripts_response) { | |
639 scripts = scripts_response.body(); | |
640 shell_.scripts = []; | |
641 for (var i in scripts) { | |
642 var script = scripts[i]; | |
643 | |
644 // Add this script to the internal list of scripts. | |
645 shell_.scripts.push(script); | |
646 | |
647 // Print result if this response was the result of a user command. | |
648 if (scripts_response.command.from_user) { | |
649 var name = script.name; | |
650 if (name) { | |
651 if (script.lineOffset > 0) { | |
652 print(name + " (lines " + script.lineOffset + "-" + | |
653 (script.lineOffset + script.lineCount - 1) + ")"); | |
654 } else { | |
655 print(name + " (lines " + script.lineCount + ")"); | |
656 } | |
657 } else { | |
658 // For unnamed scripts (typically eval) display some source. | |
659 var sourceStart = script.sourceStart; | |
660 if (sourceStart.length > 40) | |
661 sourceStart = sourceStart.substring(0, 37) + '...'; | |
662 print("[unnamed] (source:\"" + sourceStart + "\")"); | |
663 } | |
664 } | |
665 } | |
666 }; | |
667 | |
668 /** | |
669 * Parses arguments to "source" command. | |
670 * @see DebugCommand.commands | |
671 * @param {string} str - The arguments to be parsed. | |
672 * @return -1 for usage error, 1 for success | |
673 */ | |
674 DebugCommand.prototype.parseSource_ = function(str) { | |
675 this.arguments = {}; | |
676 if (this.current_frame > 0) | |
677 this.arguments.frame = this.current_frame; | |
678 if (str.length) { | |
679 var args = str.split(" "); | |
680 if (args.length == 1) { | |
681 // with 1 argument n, we print 10 lines starting at n | |
682 var num = parseInt(args[0]); | |
683 if (num > 0) { | |
684 this.arguments.fromLine = num - 1; | |
685 this.arguments.toLine = this.arguments.fromLine + 10; | |
686 } else { | |
687 return -1; | |
688 } | |
689 } else if (args.length == 2) { | |
690 // with 2 arguments x and y, we print from line x to line x + y | |
691 var from = parseInt(args[0]); | |
692 var len = parseInt(args[1]); | |
693 if (from > 0 && len > 0) { | |
694 this.arguments.fromLine = from - 1; | |
695 this.arguments.toLine = this.arguments.fromLine + len; | |
696 } else { | |
697 return -1; | |
698 } | |
699 } else { | |
700 return -1; | |
701 } | |
702 if (this.arguments.fromLine < 0) | |
703 return -1; | |
704 if (this.arguments.toLine <= this.arguments.fromLine) | |
705 return -1; | |
706 } else if (shell_.current_line > 0) { | |
707 // with no arguments, we print 11 lines with the current line as the center | |
708 this.arguments.fromLine = | |
709 Math.max(0, shell_.current_line - 6); | |
710 this.arguments.toLine = this.arguments.fromLine + 11; | |
711 } | |
712 return 1; | |
713 }; | |
714 | |
715 /** | |
716 * Handle the response to a "source" command and display output to user. | |
717 * @param {ProtocolPacket} source_response - the V8 debugger response object | |
718 */ | |
719 DebugCommand.responseSource_ = function(source_response) { | |
720 var body = source_response.body(); | |
721 var from_line = parseInt(body.fromLine) + 1; | |
722 var source = body.source; | |
723 var lines = source.split('\n'); | |
724 var maxdigits = 1 + Math.floor(DebugCommand.log10(from_line + lines.length)) | |
725 for (var num in lines) { | |
726 // there's an extra newline at the end | |
727 if (num >= (lines.length - 1) && lines[num].length == 0) | |
728 break; | |
729 spacer = maxdigits - (1 + Math.floor(DebugCommand.log10(from_line))) | |
730 var line = ""; | |
731 if (from_line == shell_.current_line) { | |
732 for (var i = 0; i < (maxdigits + 2); i++) | |
733 line += ">"; | |
734 } else { | |
735 for (var i = 0; i < spacer; i++) | |
736 line += " "; | |
737 line += from_line + ": "; | |
738 } | |
739 line += lines[num]; | |
740 print(line); | |
741 from_line++; | |
742 } | |
743 }; | |
744 | |
745 /** | |
746 * Parses arguments to "help" command. See DebugCommand.commands below | |
747 * for syntax details. | |
748 * @see DebugCommand.commands | |
749 * @param {string} str The arguments to be parsed. | |
750 * @return 0 for handled internally | |
751 */ | |
752 DebugCommand.parseHelp_ = function(str) { | |
753 DebugCommand.help(str); | |
754 return 0; | |
755 }; | |
756 | |
757 /** | |
758 * Takes argument and evaluates it in the context of the shell to allow commands | |
759 * to be escaped to the outer shell. Used primarily for development purposes. | |
760 * @see DebugCommand.commands | |
761 * @param {string} str The expression to be evaluated | |
762 * @return 0 for handled internally | |
763 */ | |
764 DebugCommand.parseShell_ = function(str) { | |
765 print(eval(str)); | |
766 return 0; | |
767 } | |
768 | |
769 DebugCommand.parseShellDebug_ = function(str) { | |
770 shell_.debug = !shell_.debug; | |
771 if (shell_.debug) { | |
772 print("shell debugging enabled"); | |
773 } else { | |
774 print("shell debugging disabled"); | |
775 } | |
776 return 0; | |
777 } | |
778 | |
779 /** | |
780 * Parses a user-entered command string. | |
781 * @param {string} str The arguments to be parsed. | |
782 */ | |
783 DebugCommand.prototype.parseArgs_ = function(str) { | |
784 if (str.length) | |
785 str = DebugCommand.trim(str); | |
786 var cmd = DebugCommand.commands[this.user_command]; | |
787 if (cmd) { | |
788 var parse = cmd['parse']; | |
789 if (parse == undefined) { | |
790 print('>>>can\'t find parse func for ' + this.user_command); | |
791 this.type = "error"; | |
792 } else { | |
793 var ret = parse.call(this, str); | |
794 if (ret > 0) { | |
795 // Command gererated a debugger request. | |
796 this.type = "request"; | |
797 } else if (ret == 0) { | |
798 // Command handeled internally. | |
799 this.type = "handled"; | |
800 } else if (ret < 0) { | |
801 // Command error. | |
802 this.type = "handled"; | |
803 DebugCommand.help(this.user_command); | |
804 } | |
805 } | |
806 } else { | |
807 this.type = "handled"; | |
808 print('unknown command: ' + this.user_command); | |
809 DebugCommand.help(); | |
810 } | |
811 }; | |
812 | |
813 /** | |
814 * Displays command help or all help. | |
815 * @param {string} opt_str Which command to print help for. | |
816 */ | |
817 DebugCommand.help = function(opt_str) { | |
818 if (opt_str) { | |
819 var cmd = DebugCommand.commands[opt_str]; | |
820 var usage = cmd.usage; | |
821 print('usage: ' + usage); | |
822 // Print additional details for the command. | |
823 if (cmd.help) { | |
824 print(cmd.help); | |
825 } | |
826 } else { | |
827 if (shell_.running) { | |
828 print('Status: page is running'); | |
829 } else { | |
830 print('Status: page is paused'); | |
831 } | |
832 print('Available commands:'); | |
833 for (var key in DebugCommand.commands) { | |
834 var cmd = DebugCommand.commands[key]; | |
835 if (!cmd['hidden'] && (!shell_.running || cmd['while_running'])) { | |
836 var usage = cmd.usage; | |
837 print(' ' + usage); | |
838 } | |
839 } | |
840 } | |
841 }; | |
842 | |
843 /** | |
844 * Valid commands, their argument parser and their associated usage text. | |
845 */ | |
846 DebugCommand.commands = { | |
847 'args': { 'parse': DebugCommand.prototype.parseArgsAndLocals_, | |
848 'usage': 'args', | |
849 'help': 'summarize the arguments to the current function.', | |
850 'response': DebugCommand.responseArgs_ }, | |
851 'break': { 'parse': DebugCommand.prototype.parseBreak_, | |
852 'response': DebugCommand.responseBreak_, | |
853 'usage': 'break [location] <condition>', | |
854 'help': 'location is one of <function> | <script:function> | <scrip
t:line> | <script:line:pos>', | |
855 'while_running': true }, | |
856 'break_info': { 'parse': DebugCommand.prototype.parseBreakInfo_, | |
857 'usage': 'break_info [breakpoint #]', | |
858 'help': 'list the current breakpoints, or the details on a sin
gle one', | |
859 'while_running': true }, | |
860 'backtrace': { 'parse': DebugCommand.prototype.parseBacktrace_, | |
861 'response': DebugCommand.responseBacktrace_, | |
862 'usage': 'backtrace [from frame #] [to frame #]' }, | |
863 'clear': { 'parse': DebugCommand.prototype.parseClearCommand_, | |
864 'response': DebugCommand.responseClear_, | |
865 'usage': 'clear <breakpoint #>', | |
866 'while_running': true }, | |
867 'continue': { 'parse': DebugCommand.prototype.parseContinueCommand_, | |
868 'usage': 'continue' }, | |
869 'dir': { 'parse': DebugCommand.prototype.parseDir_, | |
870 'response': DebugCommand.responseDir_, | |
871 'usage': 'dir <expression>', | |
872 'while_running': true }, | |
873 'frame': { 'parse': DebugCommand.prototype.parseFrame_, | |
874 'response': DebugCommand.responseFrame_, | |
875 'usage': 'frame <frame #>' }, | |
876 'help': { 'parse': DebugCommand.parseHelp_, | |
877 'usage': 'help [command]', | |
878 'while_running': true }, | |
879 'locals': { 'parse': DebugCommand.prototype.parseArgsAndLocals_, | |
880 'usage': 'locals', | |
881 'help': 'summarize the local variables for current frame', | |
882 'response': DebugCommand.responseLocals_ }, | |
883 'next': { 'parse': DebugCommand.prototype.parseNext_, | |
884 'usage': 'next' } , | |
885 'print': { 'parse': DebugCommand.prototype.parsePrint_, | |
886 'response': DebugCommand.responsePrint_, | |
887 'usage': 'print <expression>', | |
888 'while_running': true }, | |
889 'scripts': { 'parse': DebugCommand.prototype.parseScripts_, | |
890 'response': DebugCommand.responseScripts_, | |
891 'usage': 'scripts', | |
892 'while_running': true }, | |
893 'source': { 'parse': DebugCommand.prototype.parseSource_, | |
894 'response': DebugCommand.responseSource_, | |
895 'usage': 'source [from line] | [<from line> <num lines>]' }, | |
896 'step': { 'parse': DebugCommand.prototype.parseStep_, | |
897 'usage': 'step' }, | |
898 'stepout': { 'parse': DebugCommand.prototype.parseStepOut_, | |
899 'usage': 'stepout' }, | |
900 // local eval for debugging - remove this later | |
901 'shell': { 'parse': DebugCommand.parseShell_, | |
902 'usage': 'shell <expression>', | |
903 'while_running': true, | |
904 'hidden': true }, | |
905 'shelldebug': { 'parse': DebugCommand.parseShellDebug_, | |
906 'usage': 'shelldebug', | |
907 'while_running': true, | |
908 'hidden': true }, | |
909 }; | |
910 | |
911 | |
912 /** | |
913 * Debug shell using the new JSON protocol | |
914 * @param {Object} tab - which tab is to be debugged. This is an internal | |
915 * Chrome object. | |
916 * @constructor | |
917 */ | |
918 function DebugShell(tab) { | |
919 this.tab = tab; | |
920 this.tab.attach(); | |
921 this.ready = true; | |
922 this.running = true; | |
923 this.current_command = undefined; | |
924 this.pending_commands = []; | |
925 // The auto continue flag is used to indicate whether the JavaScript execution | |
926 // should automatically continue after a break event and the processing of | |
927 // pending commands. This is used to make it possible for the user to issue | |
928 // commands, e.g. setting break points, without making an explicit break. In | |
929 // this case the debugger will silently issue a forced break issue the command | |
930 // and silently continue afterwards. | |
931 this.auto_continue = false; | |
932 this.debug = false; | |
933 this.current_line = -1; | |
934 this.current_pos = -1; | |
935 this.current_frame = 0; | |
936 this.current_script = undefined; | |
937 this.scripts = []; | |
938 | |
939 // Mapping of breakpoints id --> info. | |
940 // Must use numeric keys. | |
941 this.breakpoints = []; | |
942 }; | |
943 | |
944 DebugShell.prototype.set_ready = function(ready) { | |
945 if (ready != this.ready) { | |
946 this.ready = ready; | |
947 ChromeNode.setDebuggerReady(this.ready); | |
948 } | |
949 }; | |
950 | |
951 DebugShell.prototype.set_running = function(running) { | |
952 if (running != this.running) { | |
953 this.running = running; | |
954 ChromeNode.setDebuggerBreak(!this.running); | |
955 } | |
956 }; | |
957 | |
958 /** | |
959 * Execute a constructed DebugCommand object if possible, otherwise pend. | |
960 * @param cmd {DebugCommand} - command to execute | |
961 */ | |
962 DebugShell.prototype.process_command = function(cmd) { | |
963 dprint("Running: " + (this.running ? "yes" : "no")); | |
964 | |
965 // The "break" commands needs to be handled seperatly | |
966 if (cmd.command == "break") { | |
967 if (this.running) { | |
968 // Schedule a break. | |
969 print("Stopping JavaScript execution..."); | |
970 this.tab.debugBreak(false); | |
971 } else { | |
972 print("JavaScript execution already stopped."); | |
973 } | |
974 return; | |
975 } | |
976 | |
977 // If page is running an break needs to be issued. | |
978 if (this.running) { | |
979 // Some requests are not valid when the page is running. | |
980 var cmd_info = DebugCommand.commands[cmd.user_command]; | |
981 if (!cmd_info['while_running']) { | |
982 print(cmd.user_command + " can only be run while paused"); | |
983 return; | |
984 } | |
985 | |
986 // Add the command as pending before scheduling a break. | |
987 this.pending_commands.push(cmd); | |
988 dprint("pending command: " + cmd.toJSONProtocol()); | |
989 | |
990 // Schedule a forced break and enable auto continue. | |
991 this.tab.debugBreak(true); | |
992 this.auto_continue = true; | |
993 this.set_ready(false); | |
994 return; | |
995 } | |
996 | |
997 // If waiting for a response add command as pending otherwise send the | |
998 // command. | |
999 if (this.current_command) { | |
1000 this.pending_commands.push(cmd); | |
1001 dprint("pending command: " + cmd.toJSONProtocol()); | |
1002 } else { | |
1003 this.current_command = cmd; | |
1004 cmd.sendToDebugger(this.tab); | |
1005 this.set_ready(false); | |
1006 } | |
1007 }; | |
1008 | |
1009 /** | |
1010 * Handle a break event from the debugger. | |
1011 * @param msg {ResponsePacket} - break_event protocol message to handle | |
1012 */ | |
1013 DebugShell.prototype.event_break = function(break_event) { | |
1014 this.current_frame = 0; | |
1015 this.set_running(false); | |
1016 var body = break_event.body(); | |
1017 if (body) { | |
1018 this.current_script = body.script; | |
1019 var loc = DebugCommand.getSourceLocation(body.script, | |
1020 body.sourceLineText, body.sourceLine, body.invocationText); | |
1021 var location = loc[0]; | |
1022 var source = loc[1]; | |
1023 this.current_line = loc[2]; | |
1024 if (body.breakpoints) { | |
1025 // Always disable auto continue if a real break point is hit. | |
1026 this.auto_continue = false; | |
1027 var breakpoints = body.breakpoints; | |
1028 print("paused at breakpoint " + breakpoints.join(",") + ": " + | |
1029 location); | |
1030 for (var i = 0; i < breakpoints.length; i++) | |
1031 this.didHitBreakpoint(parseInt(breakpoints[i])); | |
1032 } else if (body.scriptData == "") { | |
1033 print("paused"); | |
1034 } else { | |
1035 // step, stepout, next, "break" and a "debugger" line in the code | |
1036 // are all treated the same (they're not really distinguishable anyway) | |
1037 if (location != this.last_break_location) { | |
1038 // We only print the location (function + script) when it changes, | |
1039 // so as we step, you only see the source line when you transition | |
1040 // to a new script and/or function. Also if auto continue is enables | |
1041 // don't print the break location. | |
1042 if (!this.auto_continue) | |
1043 print(location); | |
1044 } | |
1045 } | |
1046 // Print th current source line unless auto continue is enabled. | |
1047 if (source && !this.auto_continue) | |
1048 print(source); | |
1049 this.last_break_location = location; | |
1050 } | |
1051 if (!this.auto_continue) | |
1052 this.set_ready(true); | |
1053 }; | |
1054 | |
1055 /** | |
1056 * Handle an exception event from the debugger. | |
1057 * @param msg {ResponsePacket} - exception_event protocol message to handle | |
1058 */ | |
1059 DebugShell.prototype.event_exception = function(exception_event) { | |
1060 this.set_running(false); | |
1061 this.set_ready(true); | |
1062 var body = exception_event.body(); | |
1063 if (body) { | |
1064 if (body["uncaught"]) { | |
1065 print("uncaught exception " + body["exception"].text); | |
1066 } else { | |
1067 print("paused at exception " + body["exception"].text); | |
1068 } | |
1069 } | |
1070 }; | |
1071 | |
1072 DebugShell.prototype.matchScript = function(script_match, line) { | |
1073 var script = null; | |
1074 // In the v8 debugger, all scripts have a name, line offset and line count | |
1075 // Script names are usually URLs which are a pain to have to type again and | |
1076 // again, so we match the tail end of the script name. This makes it easy | |
1077 // to type break foo.js:23 rather than | |
1078 // http://www.foo.com/bar/baz/quux/test/foo.js:23. In addition to the tail | |
1079 // of the name we also look at the lines the script cover. If there are | |
1080 // several scripts with the same tail including the requested line we match | |
1081 // the first one encountered. | |
1082 // TODO(sgjesse) Find how to handle several matching scripts. | |
1083 var candidate_scripts = []; | |
1084 for (var i in this.scripts) { | |
1085 if (this.scripts[i].name && | |
1086 this.scripts[i].name.indexOf(script_match) >= 0) { | |
1087 candidate_scripts.push(this.scripts[i]); | |
1088 } | |
1089 } | |
1090 for (var i in candidate_scripts) { | |
1091 var s = candidate_scripts[i]; | |
1092 var from = s.lineOffset; | |
1093 var to = from + s.lineCount; | |
1094 if (from <= line && line < to) { | |
1095 script = s; | |
1096 break; | |
1097 } | |
1098 } | |
1099 if (script) | |
1100 return script; | |
1101 else | |
1102 return null; | |
1103 } | |
1104 | |
1105 // The Chrome Subshell interface requires: | |
1106 // prompt(), command(), response(), exit() and on_disconnect() | |
1107 | |
1108 /** | |
1109 * Called by Chrome Shell to get a prompt string to display. | |
1110 */ | |
1111 DebugShell.prototype.prompt = function() { | |
1112 if (this.current_command) | |
1113 return ''; | |
1114 if (!this.running) | |
1115 return 'v8(paused)> '; | |
1116 else | |
1117 return 'v8(running)> '; | |
1118 }; | |
1119 | |
1120 /** | |
1121 * Called by Chrome Shell when command input has been received from the user. | |
1122 */ | |
1123 DebugShell.prototype.command = function(str) { | |
1124 if (this.tab) { | |
1125 str = DebugCommand.trim(str); | |
1126 if (str.length) { | |
1127 var cmd = new DebugCommand(str); | |
1128 cmd.from_user = true; | |
1129 if (cmd.type == "request") | |
1130 this.process_command(cmd); | |
1131 } | |
1132 } else { | |
1133 print(">>not connected to a tab"); | |
1134 } | |
1135 }; | |
1136 | |
1137 /** | |
1138 * Called by Chrome Shell when a response to a previous command has been | |
1139 * received. | |
1140 * @param {Object} msg Message object. | |
1141 */ | |
1142 DebugShell.prototype.response = function(msg) { | |
1143 dprint("received: " + (msg && msg.type)); | |
1144 var response; | |
1145 try { | |
1146 response = new ProtocolPackage(msg); | |
1147 } catch (error) { | |
1148 print(error.toString(), str); | |
1149 return; | |
1150 } | |
1151 if (response.type() == "event") { | |
1152 ev = response.event(); | |
1153 if (ev == "break") { | |
1154 this.event_break(response); | |
1155 } else if (ev == "exception") { | |
1156 this.event_exception(response); | |
1157 } | |
1158 } else if (response.type() == "response") { | |
1159 if (response.requestSeq() != undefined) { | |
1160 if (!this.current_command || this.current_command.seq != response.requestS
eq()){ | |
1161 throw("received response to unknown command " + str); | |
1162 } | |
1163 } else { | |
1164 // TODO(erikkay): should we reject these when they happen? | |
1165 print(">>no request_seq in response " + str); | |
1166 } | |
1167 var cmd = DebugCommand.commands[this.current_command.user_command] | |
1168 response.command = this.current_command; | |
1169 this.current_command = null | |
1170 this.set_running(response.running()); | |
1171 if (!response.success()) { | |
1172 print(response.message()); | |
1173 } else { | |
1174 var handler = cmd['response']; | |
1175 if (handler != undefined) { | |
1176 handler.call(this, response); | |
1177 } | |
1178 } | |
1179 this.set_ready(true); | |
1180 } | |
1181 | |
1182 // Process next pending command if any. | |
1183 if (this.pending_commands.length) { | |
1184 this.process_command(this.pending_commands.shift()); | |
1185 } else if (this.auto_continue) { | |
1186 // If no more pending commands and auto continue is active issue a continue
command. | |
1187 this.auto_continue = false; | |
1188 this.process_command(new DebugCommand("continue")); | |
1189 } | |
1190 }; | |
1191 | |
1192 /** | |
1193 * Called when a breakpoint has been set. | |
1194 * @param {BreakpointInfo} info - details of breakpoint set. | |
1195 */ | |
1196 DebugShell.prototype.addedBreakpoint = function(info) { | |
1197 print("set breakpoint #" + info.id); | |
1198 this.breakpoints[info.id] = info; | |
1199 } | |
1200 | |
1201 /** | |
1202 * Called when a breakpoint has been cleared. | |
1203 * @param {int} id - the breakpoint number that was cleared. | |
1204 */ | |
1205 DebugShell.prototype.clearedBreakpoint = function(id) { | |
1206 assertIsNumberType(id, "clearedBreakpoint called with invalid id"); | |
1207 | |
1208 print("cleared breakpoint #" + id); | |
1209 delete this.breakpoints[id]; | |
1210 } | |
1211 | |
1212 /** | |
1213 * Called when a breakpoint has been reached. | |
1214 * @param {int} id - the breakpoint number that was hit. | |
1215 */ | |
1216 DebugShell.prototype.didHitBreakpoint = function(id) { | |
1217 assertIsNumberType(id, "didHitBreakpoint called with invalid id"); | |
1218 | |
1219 var info = this.breakpoints[id]; | |
1220 if (!info) | |
1221 throw "Could not find breakpoint #" + id; | |
1222 | |
1223 info.hit_count ++; | |
1224 } | |
1225 | |
1226 /** | |
1227 * Print a summary of the specified breakpoints. | |
1228 * | |
1229 * @param {Array<BreakpointInfo>} breakpointsToPrint - List of breakpoints. The | |
1230 * index is unused (id is determined from the info). | |
1231 */ | |
1232 DebugShell.printBreakpoints_ = function(breakpoints) { | |
1233 // TODO(ericroman): this would look much nicer if we could output as an HTML | |
1234 // table. I tried outputting as formatted text table, but this looks aweful | |
1235 // once it triggers wrapping (which is very likely if the target is a script) | |
1236 | |
1237 // Output as a comma separated list of key=value | |
1238 for (var i in breakpoints) { | |
1239 var b = breakpoints[i]; | |
1240 var props = ["id", "hit_count", "type", "target", "line", "position", | |
1241 "condition"]; | |
1242 var propertyList = []; | |
1243 for (var i = 0; i < props.length; i++) { | |
1244 var prop = props[i]; | |
1245 var val = b[prop]; | |
1246 if (val != undefined) | |
1247 propertyList.push(prop + "=" + val); | |
1248 } | |
1249 print(propertyList.join(", ")); | |
1250 } | |
1251 } | |
1252 | |
1253 /** | |
1254 * Called by Chrome Shell when the outer shell is detaching from debugging | |
1255 * this tab. | |
1256 */ | |
1257 DebugShell.prototype.exit = function() { | |
1258 if (this.tab) { | |
1259 this.tab.detach(); | |
1260 this.tab = null; | |
1261 } | |
1262 }; | |
1263 | |
1264 /** | |
1265 * Called by the Chrome Shell when the tab that the shell is debugging | |
1266 * have attached. | |
1267 */ | |
1268 DebugShell.prototype.on_attach = function(title) { | |
1269 if (!title) | |
1270 title = "Untitled"; | |
1271 print('attached to ' + title); | |
1272 // on attach, we update our current script list | |
1273 var cmd = new DebugCommand("scripts"); | |
1274 cmd.from_user = false; | |
1275 this.process_command(cmd); | |
1276 }; | |
1277 | |
1278 | |
1279 /** | |
1280 * Called by the Chrome Shell when the tab that the shell is debugging | |
1281 * went away. | |
1282 */ | |
1283 DebugShell.prototype.on_disconnect = function() { | |
1284 print(">>lost connection to tab"); | |
1285 this.tab = null; | |
1286 }; | |
1287 | |
1288 | |
1289 /** | |
1290 * Protocol packages send from the debugger. | |
1291 * @param {string} json - raw protocol packet as JSON string. | |
1292 * @constructor | |
1293 */ | |
1294 function ProtocolPackage(msg) { | |
1295 this.packet_ = msg; | |
1296 this.refs_ = []; | |
1297 if (this.packet_.refs) { | |
1298 for (var i = 0; i < this.packet_.refs.length; i++) { | |
1299 this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i]; | |
1300 } | |
1301 } | |
1302 } | |
1303 | |
1304 | |
1305 /** | |
1306 * Get the packet type. | |
1307 * @return {String} the packet type | |
1308 */ | |
1309 ProtocolPackage.prototype.type = function() { | |
1310 return this.packet_.type; | |
1311 } | |
1312 | |
1313 | |
1314 /** | |
1315 * Get the packet event. | |
1316 * @return {Object} the packet event | |
1317 */ | |
1318 ProtocolPackage.prototype.event = function() { | |
1319 return this.packet_.event; | |
1320 } | |
1321 | |
1322 | |
1323 /** | |
1324 * Get the packet request sequence. | |
1325 * @return {number} the packet request sequence | |
1326 */ | |
1327 ProtocolPackage.prototype.requestSeq = function() { | |
1328 return this.packet_.request_seq; | |
1329 } | |
1330 | |
1331 | |
1332 /** | |
1333 * Get the packet request sequence. | |
1334 * @return {number} the packet request sequence | |
1335 */ | |
1336 ProtocolPackage.prototype.running = function() { | |
1337 return this.packet_.running ? true : false; | |
1338 } | |
1339 | |
1340 | |
1341 ProtocolPackage.prototype.success = function() { | |
1342 return this.packet_.success ? true : false; | |
1343 } | |
1344 | |
1345 | |
1346 ProtocolPackage.prototype.message = function() { | |
1347 return this.packet_.message; | |
1348 } | |
1349 | |
1350 | |
1351 ProtocolPackage.prototype.body = function() { | |
1352 return this.packet_.body; | |
1353 } | |
1354 | |
1355 | |
1356 ProtocolPackage.prototype.lookup = function(handle) { | |
1357 return this.refs_[handle]; | |
1358 } | |
1359 | |
1360 | |
1361 /** | |
1362 * Structure that holds the details about a breakpoint. | |
1363 * @constructor | |
1364 * | |
1365 * @param {int} id - breakpoint number | |
1366 * @param {string} type - "script" or "function" | |
1367 * @param {string} target - either a function name, or script url | |
1368 * @param {int} line - line number in the script, or undefined | |
1369 * @param {int} position - column in the script, or undefined | |
1370 * @param {string} condition - boolean expression, or undefined | |
1371 */ | |
1372 function BreakpointInfo(id, type, target, line, position, condition) { | |
1373 this.id = id; | |
1374 this.type = type; | |
1375 this.target = target; | |
1376 | |
1377 if (line != undefined) | |
1378 this.line = line; | |
1379 if (position != undefined) | |
1380 this.position = position; | |
1381 if (condition != undefined) | |
1382 this.condition = condition; | |
1383 | |
1384 this.hit_count = 0; | |
1385 | |
1386 // Check that the id is numeric, otherwise will run into problems later | |
1387 assertIsNumberType(this.id, "id is not a number"); | |
1388 } | |
1389 | |
1390 var shell_ = null; | |
1391 DebugShell.initDebugShell = function(debuggerUI) { | |
1392 if (!DebugShell.singleton) { | |
1393 DebugShell.ui = debuggerUI; | |
1394 DebugShell.singleton = new DebugShell(TabNode); | |
1395 shell_ = DebugShell.singleton; | |
1396 | |
1397 // enable debug output | |
1398 //shell_.debug = true; | |
1399 } | |
1400 }; | |
1401 | |
1402 /** | |
1403 * Print debugging message when DebugShell's debug flag is true. | |
1404 */ | |
1405 function dprint(str) { | |
1406 if (shell_ && shell_.debug) { | |
1407 print(str); | |
1408 } | |
1409 }; | |
1410 | |
1411 /** | |
1412 * Helper that throws error if x is not a number | |
1413 * @param x {object} - object to test type of | |
1414 * @param error_message {string} - error to throw on failure | |
1415 */ | |
1416 function assertIsNumberType(x, error_message) { | |
1417 if (typeof x != "number") | |
1418 throw error_message; | |
1419 } | |
1420 | |
1421 ////////////////////// migration staff ////////////////////////// | |
1422 // This file was copied from chrome\browser\debugger\resources\debugger_shell.js | |
1423 | |
1424 function print(txt) { | |
1425 var ui = DebugShell.ui; | |
1426 if (ui) { | |
1427 ui.appendText(txt); | |
1428 } | |
1429 } | |
1430 | |
1431 var TabNode = { | |
1432 debugBreak: function(force) { | |
1433 DebuggerIPC.sendMessage(["debugBreak", force]); | |
1434 }, | |
1435 attach: function() { | |
1436 DebuggerIPC.sendMessage(["attach"]); | |
1437 }, | |
1438 detach: function() { | |
1439 // TODO(yurys): send this from DebugHandler when it's being destroyed? | |
1440 DebuggerIPC.sendMessage(["detach"]); | |
1441 }, | |
1442 sendToDebugger: function(str) { | |
1443 DebuggerIPC.sendMessage(["sendToDebugger", str]); | |
1444 } | |
1445 }; | |
1446 | |
1447 var ChromeNode = { | |
1448 setDebuggerReady: function(isReady) { | |
1449 DebuggerIPC.sendMessage(["setDebuggerReady", isReady]); | |
1450 }, | |
1451 setDebuggerBreak: function(isBreak) { | |
1452 var ui = DebugShell.ui; | |
1453 if (ui) { | |
1454 ui.setDebuggerBreak(isBreak); | |
1455 } | |
1456 DebuggerIPC.sendMessage(["setDebuggerBreak", isBreak]); | |
1457 } | |
1458 }; | |
OLD | NEW |