Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(646)

Side by Side Diff: chrome/tools/test/reference_build/chrome_linux/resources/inspector/DebuggerShell.js

Issue 177049: On Linux, move the passing of filedescriptors to a dedicated socketpair(). (Closed)
Patch Set: Removed *.d files from reference build Created 11 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698