diff options
author | dglazkov@google.com <dglazkov@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-10 19:11:09 +0000 |
---|---|---|
committer | dglazkov@google.com <dglazkov@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-10 19:11:09 +0000 |
commit | 15afbb51fd02caf9baebd5e406ec88bf59d8f835 (patch) | |
tree | b0e5ce4fb8c334e6e52e94f5d3afcabb45097b8f /webkit | |
parent | 2a65160320c17d0c0f370d71fac51d6250b5b1fa (diff) | |
download | chromium_src-15afbb51fd02caf9baebd5e406ec88bf59d8f835.zip chromium_src-15afbb51fd02caf9baebd5e406ec88bf59d8f835.tar.gz chromium_src-15afbb51fd02caf9baebd5e406ec88bf59d8f835.tar.bz2 |
Move forked, DevTools-specific JS files out of third_party/WebKit
R=darin
BUG=3320
Review URL: http://codereview.chromium.org/44001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@11358 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r-- | webkit/build/port/port.vcproj | 13 | ||||
-rw-r--r-- | webkit/inspector/DebuggerConsole.js | 173 | ||||
-rw-r--r-- | webkit/inspector/DebuggerIPC.js | 109 | ||||
-rw-r--r-- | webkit/inspector/DebuggerPanel.js | 201 | ||||
-rw-r--r-- | webkit/inspector/DebuggerShell.js | 1458 | ||||
-rw-r--r-- | webkit/inspector/README | 11 | ||||
-rw-r--r-- | webkit/inspector/debugger.css | 35 | ||||
-rw-r--r-- | webkit/inspector/debugger.html | 31 |
8 files changed, 2024 insertions, 7 deletions
diff --git a/webkit/build/port/port.vcproj b/webkit/build/port/port.vcproj index 996d47c..a5e1dcf 100644 --- a/webkit/build/port/port.vcproj +++ b/webkit/build/port/port.vcproj @@ -110,28 +110,27 @@ > </File> <File - RelativePath="..\..\..\third_party\WebKit\WebCore\inspector\front-end\debugger.css" + RelativePath="..\..\inspector\debugger.css" > </File> <File - RelativePath="..\..\..\third_party\WebKit\WebCore\inspector\front-end\debugger.html" + RelativePath="..\..\inspector\debugger.html" > </File> <File - RelativePath="..\..\..\third_party\WebKit\WebCore\inspector\front-end\DebuggerConsole.js" + RelativePath="..\..\inspector\DebuggerConsole.js" > </File> <File - RelativePath="..\..\..\third_party\WebKit\WebCore\inspector\front-end\DebuggerIPC.js" + RelativePath="..\..\inspector\DebuggerIPC.js" > </File> <File - RelativePath="..\..\..\third_party\WebKit\WebCore\inspector\front-end\DebuggerPanel.js" - DeploymentContent="true" + RelativePath="..\..\inspector\DebuggerPanel.js" > </File> <File - RelativePath="..\..\..\third_party\WebKit\WebCore\inspector\front-end\DebuggerShell.js" + RelativePath="..\..\inspector\DebuggerShell.js" > </File> <File diff --git a/webkit/inspector/DebuggerConsole.js b/webkit/inspector/DebuggerConsole.js new file mode 100644 index 0000000..0ba3b28 --- /dev/null +++ b/webkit/inspector/DebuggerConsole.js @@ -0,0 +1,173 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Helper functions and objects for the JS debugger UI. + * @see debugger.html + */ + +/** + * Document load listener. + */ +function onLoad() { + var debuggerConsole = new DebuggerConsole(); + DebuggerIPC.init(debuggerConsole); + DebugShell.initDebugShell(debuggerConsole); + debuggerConsole.focusOnCommandLine(); +} + +/** + * @constructor + */ +function DebuggerConsole() +{ + this._output = document.getElementById("output"); + + var input = document.getElementById("command-line-text"); + var self = this; + input.addEventListener( + 'keydown', + function(e) { + return self._onInputKeyDown(e); + }, + true); + input.addEventListener( + 'keypress', + function(e) { + return self._onInputKeyPress(e); + }, + true); + this._commandLineInput = input; + + // command object stores command-line history state. + this._command = { + history: [], + history_index: 0, + pending: null + }; +}; + +DebuggerConsole.prototype = { + /** + * Sets focus to command-line-text element. + */ + focusOnCommandLine: function() { + this._commandLineInput.focus(); + }, + + /** + * Called by chrome code when there's output to display. + * @param {string} txt + */ + appendText: function(txt) + { + this._output.appendChild(document.createTextNode(txt)); + this._output.appendChild(document.createElement('br')); + document.body.scrollTop = document.body.scrollHeight; + }, + + /** + * Called by chrome code to set the current state as to whether the debugger + * is stopped at a breakpoint or is running. + * @param {boolean} isBroken + */ + setDebuggerBreak: function(isBroken) + { + var out = this._output; + if (isBroken) { + out.style.color = "black"; + this.focusOnCommandLine(); + } else { + out.style.color = "gray"; + } + }, + + /** + * Execute a debugger command, add it to the command history and display it in + * the output window. + * @param {string} str + */ + executeCommand: function(str) { + this.appendText("$ " + str); + // Sends message to DebuggerContents.HandleCommand. + if (DebugShell.singleton) { + DebugShell.singleton.command(str); + } else { + this.appendText("FAILED to send the command as DebugShell is null"); + } + + this._command.history.push(str); + this._command.history_index = this._command.history.length; + this._command.pending = null; + }, + + + /** + * Display the previous history item in the given text field. + * @param {HTMLInputElement} field + */ + selectPreviousCommand_: function(field) { + var command = this._command; + if (command.history_index > 0) { + // Remember the current field value as a pending command if we're at the + // end (it's something the user typed in). + if (command.history_index == command.history.length) + command.pending = field.value; + command.history_index--; + field.value = command.history[command.history_index]; + field.select(); + } + }, + + /** + * Display the next history item in the given text field. + * @param {HTMLInputElement} field + */ + selectNextCommand_: function(field) { + var command = this._command; + if (command.history_index < command.history.length) { + command.history_index++; + if (command.history_index == command.history.length) { + field.value = command.pending || ""; + } else { + field.value = command.history[command.history_index]; + } + field.select(); + } + }, + + /** + * command-line-text's onkeydown handler. + * @param {KeyboardEvent} e + * @return {boolean} + */ + _onInputKeyDown: function (e) { + var field = e.target; + var key = e.keyCode; + if (key == 38) { // up arrow + this.selectPreviousCommand_(field); + return false; + } else if (key == 40) { // down arrow + this.selectNextCommand_(field); + return false; + } + return true; + }, + + /** + * command-line-text's onkeypress handler. + * @param {KeyboardEvent} e + * @return {boolean} + */ + _onInputKeyPress: function (e) { + var field = e.target; + var key = e.keyCode; + if (key == 13) { // enter + this.executeCommand(field.value); + field.value = ""; + return false; + } + return true; + } +}; diff --git a/webkit/inspector/DebuggerIPC.js b/webkit/inspector/DebuggerIPC.js new file mode 100644 index 0000000..15835fc --- /dev/null +++ b/webkit/inspector/DebuggerIPC.js @@ -0,0 +1,109 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Implementation of debugger inter-process communication. + * Debugger UI can send messages to the DebuggerHost object living in + * Chrome browser process. The messages are either processed by DebuggerHost + * itself or trigger IPC message(such as break, evaluate script etc) from + * browser process to the renderer where the v8 instance being debugged + * will process them. + */ + +var DebuggerIPC = {}; + +/** + * Set up default debugger UI. + * @param {DebuggerPanel|DebuggerConsole} debuggerUI + */ +DebuggerIPC.init = function(debuggerUI) { + DebuggerIPC.debuggerUI = debuggerUI; +} + +/** + * Sends JSON message to DebuggerHost. + * @param {Array.<Object>} nameAndArguments + */ +DebuggerIPC.sendMessage = function(nameAndArguments) { + //alert("DebuggerIPC.sendMessage " + nameAndArguments); + dprint("DebuggerIPC.callMethod([" + nameAndArguments + "])"); + // convert all arguments to strings + // TODO(yurys): extend chrome.send to accept array of any value + // objects not only strings + for (var i = 0; i < nameAndArguments.length; i++) { + if (typeof nameAndArguments[i] != "string") { + nameAndArguments[i] = "" + nameAndArguments[i]; + } + } + + chrome.send("DebuggerHostMessage", nameAndArguments); +}; + +/** + * Handles messages from DebuggerHost. + * @param {Object} msg An object representing the message. + */ +DebuggerIPC.onMessageReceived = function(msg) { + //alert("DebuggerIPC.onMessageReceived " + msg.event); + var ui = DebuggerIPC.debuggerUI; + dprint("onMessageReceived: " + (msg && msg.event)); + if (msg.type == "event") { + if (msg.event == "setDebuggerBreak") { + var val = msg.body.argument; + ui.setDebuggerBreak(val); + } else if (msg.event == "appendText") { + var text = msg.body.text; + ui.appendText(text); + } else if (msg.event == "focusOnCommandLine") { + dprint("focusOnCommandLine event received"); + // messages to DebugShell + } else if (msg.event == "initDebugShell") { + // DebugShell.initDebugShell(); + dprint(msg.event + " done"); + } else if (msg.event == "on_attach") { + if (DebugShell.singleton) { + var args = msg.body.arguments; + if (!args || args.length != 1) { + dprint(msg.event + " failed: invalid arguments"); + return; + } + var title = args[0]; + DebugShell.singleton.on_attach(title); + dprint(msg.event + " done"); + } else { + dprint(msg.event + " failed: DebugShell.singleton == null"); + } + } else if (msg.event == "on_disconnect") { + if (DebugShell.singleton) { + DebugShell.singleton.on_disconnect(); + dprint(msg.event + " done"); + } else { + dprint(msg.event + " failed: DebugShell.singleton == null"); + } + } else if (msg.event == "exit") { + if (DebugShell.singleton) { + DebugShell.singleton.exit(); + dprint(msg.event + " done"); + } else { + dprint(msg.event + " failed: DebugShell.singleton == null"); + } + } else if (msg.event == "response") { + if (DebugShell.singleton) { + var args = msg.body.arguments; + if (!args || args.length != 1) { + dprint(msg.event + " failed: invalid argument"); + return; + } + DebugShell.singleton.response(args[0]); + dprint(msg.event + " done"); + } else { + ui.appendText(msg.event + " failed: DebugShell.singleton == null"); + } + } else { + ui.appendText("Unknown event: " + msg.event); + } + } else { + ui.appendText("Unknown message type: " + msg.type); + } +}; diff --git a/webkit/inspector/DebuggerPanel.js b/webkit/inspector/DebuggerPanel.js new file mode 100644 index 0000000..bb059ac --- /dev/null +++ b/webkit/inspector/DebuggerPanel.js @@ -0,0 +1,201 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview WebInspector panel representing command line debugger. + */ + +/** + * Command line debugger panel. + * @constructor + * @extends {WebInspector.Panel} + */ +WebInspector.DebuggerPanel = function() +{ + WebInspector.Panel.call(this); + + this.contentElement = document.createElement("div"); + this.contentElement.id = "debugger-content"; + + var table = document.createElement("table"); + table.id = 'outer'; + var tr = document.createElement("tr"); + this._output = document.createElement("td"); + this._output.id = "output"; + this._output.valign = "bottom"; + this.appendText("Chrome JavaScript Debugger"); + this.appendText("Type 'help' for a list of commands."); + + tr.appendChild(this._output); + table.appendChild(tr); + this.contentElement.appendChild(table); + + + var commandLine = document.createElement("div"); + commandLine.id = "command-line"; + var input = document.createElement("input"); + input.id = "command-line-text"; + input.addEventListener('keydown', this._onInputKeyDown.bind(this), true); + input.addEventListener('keypress', this._onInputKeyPress.bind(this), true); + input.type = "text"; + this.commandLineInput_ = input; + + commandLine.appendChild(input); + this.contentElement.appendChild(commandLine); + + // command object stores command-line history state. + this._command = { + history: [], + history_index: 0, + pending: null + }; + + this.element.appendChild(this.contentElement); + p('DebuggerPanel created'); +}; + +WebInspector.DebuggerPanel.prototype = { + toolbarItemClass: "debugger", + + get toolbarItemLabel() + { + return "Debugger"; //WebInspector.UIString("Debugger"); + }, + + show: function() + { + WebInspector.Panel.prototype.show.call(this); + this.focusOnCommandLine(); + }, + + /** + * Sets focus to command-line-text element. + */ + focusOnCommandLine: function() + { + if (this.visible) { + this.commandLineInput_.focus(); + } + }, + + /** + * Called by chrome code when there's output to display. + * @param {string} txt + */ + appendText: function(txt) + { + this._output.appendChild(document.createTextNode(txt)); + this._output.appendChild(document.createElement('br')); + }, + + /** + * Called by chrome code to set the current state as to whether the debugger + * is stopped at a breakpoint or is running. + * @param {boolean} isBroken + */ + setDebuggerBreak: function(isBroken) + { + var out = this._output; + if (isBroken) { + out.style.color = "black"; + this.focusOnCommandLine(); + } else { + out.style.color = "gray"; + } + }, + + /** + * Execute a debugger command, add it to the command history and display + * it in the output window. + * @param {string} str + */ + executeCommand: function(str) + { + this.appendText("$ " + str); + // Sends message to DebuggerContents.HandleCommand. + if (DebugShell.singleton) { + DebugShell.singleton.command(str); + } else { + this.appendText("FAILED to send the command as DebugShell is null"); + } + + this._command.history.push(str); + this._command.history_index = this._command.history.length; + this._command.pending = null; + }, + + /** + * Display the previous history item in the given text field. + * @param {HTMLInputElement} field + */ + _selectPreviousCommand: function(field) + { + var command = this._command; + if (command.history_index > 0) { + // Remember the current field value as a pending command if we're at the + // end (it's something the user typed in). + if (command.history_index == command.history.length) + command.pending = field.value; + command.history_index--; + field.value = command.history[command.history_index]; + field.select(); + } + }, + + /** + * Display the next history item in the given text field. + * @param {HTMLInputElement} field + */ + _selectNextCommand: function(field) + { + var command = this._command; + if (command.history_index < command.history.length) { + command.history_index++; + if (command.history_index == command.history.length) { + field.value = command.pending || ""; + } else { + field.value = command.history[command.history_index]; + } + field.select(); + } + }, + + /** + * command-line-text's onkeydown handler. + * @param {KeyboardEvent} e + * @return {boolean} + */ + _onInputKeyDown: function (e) + { + var field = e.target; + var key = e.keyCode; + if (key == 38) { // up arrow + this._selectPreviousCommand(field); + return false; + } else if (key == 40) { // down arrow + this._selectNextCommand(field); + return false; + } + return true; + }, + + /** + * command-line-text's onkeypress handler. + * @param {KeyboardEvent} e + * @return {boolean} + */ + _onInputKeyPress: function (e) + { + var field = e.target; + var key = e.keyCode; + if (key == 13) { // enter + this.executeCommand(field.value); + field.value = ""; + return false; + } + return true; + } +}; + +WebInspector.DebuggerPanel.prototype.__proto__ = WebInspector.Panel.prototype; diff --git a/webkit/inspector/DebuggerShell.js b/webkit/inspector/DebuggerShell.js new file mode 100644 index 0000000..907dce7 --- /dev/null +++ b/webkit/inspector/DebuggerShell.js @@ -0,0 +1,1458 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Shell objects and global helper functions for Chrome + * automation shell / debugger. This file is loaded into the global namespace + * of the interactive shell, so users can simply call global functions + * directly. + */ + +// TODO(erikkay): look into how this can be split up into multiple files +// It's currently loaded explicitly by Chrome, so maybe I need an "include" +// or "source" builtin to allow a core source file to reference multiple +// sub-files. + +/** + * Sequence number of the DebugCommand. + */ +DebugCommand.next_seq_ = 0; + +/** + * Command messages to be sent to the debugger. + * @constructor + */ +function DebugCommand(str) { + this.command = undefined; + // first, strip off of the leading word as the command + var argv = str.split(' '); + this.user_command = argv.shift(); + // the rest of the string is argv to the command + str = argv.join(' '); + if (DebugCommand.aliases[this.user_command]) + this.user_command = DebugCommand.aliases[this.user_command]; + if (this.parseArgs_(str) == 1) + this.type = "request"; + if (this.command == undefined) + this.command = this.user_command; +}; + +// Mapping of some control characters to avoid the \uXXXX syntax for most +// commonly used control cahracters. +const ctrlCharMap_ = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' +}; + +// Regular expression matching ", \ and control characters (0x00 - 0x1F) +// globally. +const ctrlCharMatch_ = /["\\\\\x00-\x1F]/g; + +/** + * Convert a String to its JSON representation. + * @param {String} value - String to be converted + * @return {String} JSON formatted String + */ +DebugCommand.stringToJSON = function(value) { + // Check for" , \ and control characters (0x00 - 0x1F). + if (ctrlCharMatch_.test(value)) { + // Replace ", \ and control characters (0x00 - 0x1F). + return '"' + value.replace(ctrlCharMatch_, function (char) { + // Use charmap if possible. + var mapped = ctrlCharMap_[char]; + if (mapped) return mapped; + mapped = char.charCodeAt(); + // Convert control character to unicode escape sequence. + var dig1 = (Math.floor(mapped / 16)); + var dig2 = (mapped % 16) + return '\\u00' + dig1.toString(16) + dig2.toString(16); + }) + + '"'; + } + + // Simple string with no special characters. + return '"' + value + '"'; +}; + +/** + * @return {bool} True if x is an integer. + */ +DebugCommand.isInt = function(x) { + var y = parseInt(x); + if (isNaN(y)) + return false; + return x == y && x.toString() == y.toString(); +}; + +/** + * @return {float} log base 10 of num + */ +DebugCommand.log10 = function(num) { + return Math.log(num)/Math.log(10); +}; + +/** + * Take an object and encode it (non-recursively) as a JSON dict. + * @param {Object} obj - object to encode + */ +DebugCommand.toJSON = function(obj) { + // TODO(erikkay): use a real JSON library + var json = '{'; + for (var key in obj) { + if (json.length > 1) + json += ","; + var val = obj[key]; + if (!DebugCommand.isInt(val)) { + val = DebugCommand.stringToJSON(val.toString()); + } + json += '"' + key + '":' + val; + } + json += '}'; + return json; +}; + +/** + * Encode the DebugCommand object into the V8 debugger JSON protocol format. + */ +DebugCommand.prototype.toJSONProtocol = function() { + // TODO(erikkay): use a real JSON library + var json = '{'; + json += '"seq":"' + this.seq; + json += '","type":"' + this.type; + json += '","command":"' + this.command + '"'; + if (this.arguments) { + json += ',"arguments":' + DebugCommand.toJSON(this.arguments); + } + json += '}' + return json; +} + +/** + * Encode the contents of this message and send it to the debugger. + * @param {Object} tab - tab being debugged. This is an internal + * Chrome object. + */ +DebugCommand.prototype.sendToDebugger = function(tab) { + this.seq = DebugCommand.next_seq_++; + str = this.toJSONProtocol(); + dprint("sending: " + str); + tab.sendToDebugger(str); +}; + +DebugCommand.trim = function(str) { + return str.replace(/^\s*/, '').replace(/\s*$/, ''); +}; + +/** + * Strip off a trailing parameter after a ':'. As the identifier for the + * source can contain ':' characters (e.g. 'http://www....) something after + * a ':' is only considered a parameter if it is numeric. + * @return {Array} two element array, the trimmed string and the parameter, + * or -1 if no parameter + */ +DebugCommand.stripTrailingParameter = function(str, opt_separator) { + var sep = opt_separator || ':'; + var index = str.lastIndexOf(sep); + // If a separator character if found strip if numeric. + if (index != -1) { + var value = parseInt(str.substring(index + 1, str.length), 10); + if (isNaN(value) || value < 0) { + return [str, -1]; + } + str = str.substring(0, index); + return [str, value]; + } + return [str, -1]; +}; + +/** + * Format source and location strings based on source location input data. + * @param {Object} script - script information object + * @param {String} source - source code for the current location + * @param {int} line - line number (0-based) + * @param {String} func - function name + * @return {array} [location(string), source line(string), line number(int)] + */ +DebugCommand.getSourceLocation = function(script, source, line, func) { + // source line is 0-based, we present as 1-based + line++; + + // TODO(erikkay): take column into account as well + if (source) + source = "" + line + ": " + source; + var location = ''; + if (func) { + location = func + ", "; + } + location += script ? script.name : '[no source]'; + return [location, source, line]; +}; + +/** + * Aliases for debugger commands. + */ +DebugCommand.aliases = { + 'b': 'break', + 'bi': 'break_info', + 'br': 'break', + 'bt': 'backtrace', + 'c': 'continue', + 'f': 'frame', + 'h': 'help', + '?': 'help', + 'ls': 'source', + 'n': 'next', + 'p': 'print', + 's': 'step', + 'so': 'stepout', +}; + +/** + * Parses arguments to "args" and "locals" command, and initializes + * the underlying DebugCommand (which is a frame request). + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return -1 for usage error, 1 for success + */ +DebugCommand.prototype.parseArgsAndLocals_ = function(str) { + this.command = "frame"; + return str.length ? -1 : 1; +}; + +/** + * Parses arguments to "break_info" command, and executes it. + * "break_info" has an optional argument, which is the breakpoint + * identifier. + * @see DebugCommand.commands + * @param {string} str - The arguments to be parsed. + * @return -1 for usage error, 0 for success + */ +DebugCommand.prototype.parseBreakInfo_ = function(str) { + this.type = "shell"; + + // Array of breakpoints to be printed by this command + // (default to all breakpoints) + var breakpointsToPrint = shell_.breakpoints; + + if (str.length > 0) { + // User specified an invalid breakpoint (not a number) + if (!str.match(/^\s*\d+\s*$/)) + return -1; // invalid usage + + // Check that the specified breakpoint identifier exists + var id = parseInt(str); + var info = shell_.breakpoints[id]; + if (!info) { + print("Error: Invalid breakpoint"); + return 0; // success (of sorts) + } + breakpointsToPrint = [info]; + } else { + // breakpointsToPrint.length isn't accurate, because of + // deletions + var num_breakpoints = 0; + for (var i in breakpointsToPrint) num_breakpoints++; + + print("Num breakpoints: " + num_breakpoints); + } + + DebugShell.printBreakpoints_(breakpointsToPrint); + + return 0; // success +} + +/** + * Parses arguments to "step" command. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return -1 for usage error, 1 for success + */ +DebugCommand.prototype.parseStep_ = function(str, opt_stepaction) { + this.command = "continue"; + action = opt_stepaction || "in"; + this.arguments = {"stepaction" : action} + if (str.length) { + count = parseInt(str); + if (count > 0) { + this.arguments["stepcount"] = count; + } else { + return -1; + } + } + return 1; +}; + +/** + * Parses arguments to "step" command. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return -1 for usage error, 1 for success + */ +DebugCommand.prototype.parseStepOut_ = function(str) { + return this.parseStep_(str, "out"); +}; + +/** + * Parses arguments to "next" command. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return -1 for usage error, 1 for success + */ +DebugCommand.prototype.parseNext_ = function(str) { + return this.parseStep_(str, "next"); +}; + +/** + * Parse the arguments to "print" command. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return 1 - always succeeds + */ +DebugCommand.prototype.parsePrint_ = function(str) { + this.command = "evaluate"; + this.arguments = { "expression" : str }; + // If the page is in the running state, then we force the expression to + // evaluate in the global context to avoid evaluating in a random context. + if (shell_.running) + this.arguments["global"] = true; + return 1; +}; + +/** + * Handle the response to a "print" command and display output to user. + * @param {ProtocolPacket} evaluate_response - the V8 debugger response object + */ +DebugCommand.responsePrint_ = function(evaluate_response) { + body = evaluate_response.body(); + if (body['text'] != undefined) { + print(body['text']); + } else { + // TODO(erikkay): is "text" ever not set? + print("can't print response"); + } +}; + +/** + * Parse the arguments to "dir" command. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return 1 - always succeeds + */ +DebugCommand.prototype.parseDir_ = function(str) { + this.command = "evaluate"; + this.arguments = { "expression" : str }; + // If the page is in the running state, then we force the expression to + // evaluate in the global context to avoid evaluating in a random context. + if (shell_.running) + this.arguments["global"] = true; + return 1; +}; + +/** + * Handle the response to a "dir" command and display output to user. + * @see http://wiki/Main/V8Debugger + * @param {ProtocolPacket} evaluate_response - the V8 debugger response object + */ +DebugCommand.responseDir_ = function(evaluate_response) { + var body = evaluate_response.body(); + if (body.properties) { + print(body.properties.length + ' properties'); + for (var n in body.properties) { + var property_info = body.properties[n].name; + property_info += ': '; + var value = evaluate_response.lookup(body.properties[n].ref); + if (value && value.type) { + property_info += value.type; + } else { + property_info += '<no type>'; + } + property_info += ' (#'; + property_info += body.properties[n].ref; + property_info += '#)'; + print(property_info); + } + } +}; + +/** + * Parses arguments to "break" command. See DebugCommand.commands below + * for syntax details. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return -1 for usage error, 1 for success, 0 for handled internally + */ +DebugCommand.prototype.parseBreak_ = function(str) { + function stripTrailingParameter() { + var ret = DebugCommand.stripTrailingParameter(str, ':'); + str = ret[0]; + return ret[1]; + } + + if (str.length == 0) { + this.command = "break"; + return 1; + } else { + var parts = str.split(/\s+/); + var condition = null; + if (parts.length > 1) { + str = parts.shift(); + condition = parts.join(" "); + } + + this.command = "setbreakpoint"; + + // Locate ...[:line[:column]] if present. + var line = -1; + var column = -1; + line = stripTrailingParameter(); + if (line != -1) { + line -= 1; + var l = stripTrailingParameter(); + if (l != -1) { + column = line; + line = l - 1; + } + } + + if (line == -1 && column == -1) { + this.arguments = { 'type' : 'function', + 'target' : str }; + } else { + var script = shell_.matchScript(str, line); + if (script) { + this.arguments = { 'type' : 'script', + 'target' : script.name }; + } else { + this.arguments = { 'type' : 'function', + 'target' : str }; + } + this.arguments.line = line; + if (column != -1) + this.arguments.position = column; + } + if (condition) + this.arguments.condition = condition; + } + return 1; +}; + +/** + * Handle the response to a "break" command and display output to user. + * @param {ResponsePacket} setbreakpoint_response - the V8 debugger response + * object + */ +DebugCommand.responseBreak_ = function(setbreakpoint_response) { + var body = setbreakpoint_response.body(); + var info = new BreakpointInfo( + parseInt(body.breakpoint), + setbreakpoint_response.command.arguments.type, + setbreakpoint_response.command.arguments.target, + setbreakpoint_response.command.arguments.line, + setbreakpoint_response.command.arguments.position, + setbreakpoint_response.command.arguments.condition); + shell_.addedBreakpoint(info); +}; + +/** + * Parses arguments to "backtrace" command. See DebugCommand.commands below + * for syntax details. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return -1 for usage error, 1 for success + */ + DebugCommand.prototype.parseBacktrace_ = function(str) { + if (str.length > 0) { + var parts = str.split(/\s+/); + var non_empty_parts = parts.filter(function(s) { return s.length > 0; }); + // We need exactly two arguments. + if (non_empty_parts.length != 2) { + return -1; + } + var from = parseInt(non_empty_parts[0], 10); + var to = parseInt(non_empty_parts[1], 10); + // The two arguments have to be integers. + if (from != non_empty_parts[0] || to != non_empty_parts[1]) { + return -1; + } + this.arguments = { 'fromFrame': from, 'toFrame': to + 1 }; + } else { + // Default to fetching the first 10 frames. + this.arguments = { 'fromFrame': 0, 'toFrame': 10 }; + } + return 1; +}; + +/** + * Handle the response to a "backtrace" command and display output to user. + * @param {ResponsePacket} backtrace_response - the V8 debugger response object + */ +DebugCommand.responseBacktrace_ = function(backtrace_response) { + body = backtrace_response.body(); + if (body && body.totalFrames) { + print('Frames #' + body.fromFrame + ' to #' + (body.toFrame - 1) + + ' of ' + body.totalFrames + ":"); + for (var i = 0; i < body.frames.length; i++) { + print(body.frames[i].text); + } + } else { + print("unimplemented (sorry)"); + } +}; + + +/** + * Parses arguments to "clear" command. See DebugCommand.commands below + * for syntax details. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return -1 for usage error, 1 for success + */ +DebugCommand.prototype.parseClearCommand_ = function(str) { + this.command = "clearbreakpoint"; + if (str.length > 0) { + var i = parseInt(str, 10); + if (i != str) { + return -1; + } + this.arguments = { 'breakpoint': i }; + } + return 1; +} + +/** + * Handle the response to a "clear" command and display output to user. + * @param {ResponsePacket} clearbreakpoint_response - the V8 debugger response + * object + */ +DebugCommand.responseClear_ = function(clearbreakpoint_response) { + var body = clearbreakpoint_response.body(); + shell_.clearedBreakpoint(parseInt(msg.command.arguments.breakpoint)); +} + + +/** + * Parses arguments to "continue" command. See DebugCommand.commands below + * for syntax details. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return -1 for usage error, 1 for success + */ +DebugCommand.prototype.parseContinueCommand_ = function(str) { + this.command = "continue"; + if (str.length > 0) { + return -1; + } + return 1; +} + +/** + * Parses arguments to "frame" command. See DebugCommand.commands below + * for syntax details. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return -1 for usage error, 1 for success + */ +DebugCommand.prototype.parseFrame_ = function(str) { + if (str.length > 0) { + var i = parseInt(str, 10); + if (i != str) { + return -1; + } + this.arguments = { 'number': i }; + } + return 1; +}; + +/** + * Handle the response to a "frame" command and display output to user. + * @param {ResponsePacket} frame_response - the V8 debugger response object + */ +DebugCommand.responseFrame_ = function(frame_response) { + var body = frame_response.body(); + var func = frame_response.lookup(body.func.ref); + loc = DebugCommand.getSourceLocation(func.script, + body.sourceLineText, body.line, func.name); + print("#" + (body.index <= 9 ? '0' : '') + body.index + " " + loc[0]); + print(loc[1]); + shell_.current_frame = body.index; + shell_.current_line = loc[2]; + shell_.current_script = func.script; +}; + +/** + * Handle the response to a "args" command and display output to user. + * @param {ProtocolPacket} frame_response - the V8 debugger response object (for + * "frame" command) + */ +DebugCommand.responseArgs_ = function(frame_response) { + var body = frame_response.body(); + DebugCommand.printVariables_(body.arguments, frame_response); +} + +/** + * Handle the response to a "locals" command and display output to user. + * @param {Object} msg - the V8 debugger response object (for "frame" command) + */ +DebugCommand.responseLocals_ = function(frame_response) { + var body = frame_response.body(); + DebugCommand.printVariables_(body.locals, frame_response); +} + +DebugCommand.printVariables_ = function(variables, protocol_packet) { + for (var i = 0; i < variables.length; i++) { + print(variables[i].name + " = " + + DebugCommand.toPreviewString_(protocol_packet.lookup(variables[i].value.ref))); + } +} + +DebugCommand.toPreviewString_ = function(value) { + // TODO(ericroman): pretty print arrays and objects, recursively. + // TODO(ericroman): truncate length of preview if too long? + if (value.type == "string") { + // Wrap the string in quote marks and JS-escape + return DebugCommand.stringToJSON(value.text); + } + return value.text; +} + +/** + * Parses arguments to "scripts" command. + * @see DebugCommand.commands + * @param {string} str - The arguments to be parsed. + * @return -1 for usage error, 1 for success + */ +DebugCommand.prototype.parseScripts_ = function(str) { + return 1 +}; + +/** + * Handle the response to a "scripts" command and display output to user. + * @param {ResponsePacket} scripts_response - the V8 debugger response object + */ +DebugCommand.responseScripts_ = function(scripts_response) { + scripts = scripts_response.body(); + shell_.scripts = []; + for (var i in scripts) { + var script = scripts[i]; + + // Add this script to the internal list of scripts. + shell_.scripts.push(script); + + // Print result if this response was the result of a user command. + if (scripts_response.command.from_user) { + var name = script.name; + if (name) { + if (script.lineOffset > 0) { + print(name + " (lines " + script.lineOffset + "-" + + (script.lineOffset + script.lineCount - 1) + ")"); + } else { + print(name + " (lines " + script.lineCount + ")"); + } + } else { + // For unnamed scripts (typically eval) display some source. + var sourceStart = script.sourceStart; + if (sourceStart.length > 40) + sourceStart = sourceStart.substring(0, 37) + '...'; + print("[unnamed] (source:\"" + sourceStart + "\")"); + } + } + } +}; + +/** + * Parses arguments to "source" command. + * @see DebugCommand.commands + * @param {string} str - The arguments to be parsed. + * @return -1 for usage error, 1 for success + */ +DebugCommand.prototype.parseSource_ = function(str) { + this.arguments = {}; + if (this.current_frame > 0) + this.arguments.frame = this.current_frame; + if (str.length) { + var args = str.split(" "); + if (args.length == 1) { + // with 1 argument n, we print 10 lines starting at n + var num = parseInt(args[0]); + if (num > 0) { + this.arguments.fromLine = num - 1; + this.arguments.toLine = this.arguments.fromLine + 10; + } else { + return -1; + } + } else if (args.length == 2) { + // with 2 arguments x and y, we print from line x to line x + y + var from = parseInt(args[0]); + var len = parseInt(args[1]); + if (from > 0 && len > 0) { + this.arguments.fromLine = from - 1; + this.arguments.toLine = this.arguments.fromLine + len; + } else { + return -1; + } + } else { + return -1; + } + if (this.arguments.fromLine < 0) + return -1; + if (this.arguments.toLine <= this.arguments.fromLine) + return -1; + } else if (shell_.current_line > 0) { + // with no arguments, we print 11 lines with the current line as the center + this.arguments.fromLine = + Math.max(0, shell_.current_line - 6); + this.arguments.toLine = this.arguments.fromLine + 11; + } + return 1; +}; + +/** + * Handle the response to a "source" command and display output to user. + * @param {ProtocolPacket} source_response - the V8 debugger response object + */ +DebugCommand.responseSource_ = function(source_response) { + var body = source_response.body(); + var from_line = parseInt(body.fromLine) + 1; + var source = body.source; + var lines = source.split('\n'); + var maxdigits = 1 + Math.floor(DebugCommand.log10(from_line + lines.length)) + for (var num in lines) { + // there's an extra newline at the end + if (num >= (lines.length - 1) && lines[num].length == 0) + break; + spacer = maxdigits - (1 + Math.floor(DebugCommand.log10(from_line))) + var line = ""; + if (from_line == shell_.current_line) { + for (var i = 0; i < (maxdigits + 2); i++) + line += ">"; + } else { + for (var i = 0; i < spacer; i++) + line += " "; + line += from_line + ": "; + } + line += lines[num]; + print(line); + from_line++; + } +}; + +/** + * Parses arguments to "help" command. See DebugCommand.commands below + * for syntax details. + * @see DebugCommand.commands + * @param {string} str The arguments to be parsed. + * @return 0 for handled internally + */ +DebugCommand.parseHelp_ = function(str) { + DebugCommand.help(str); + return 0; +}; + +/** + * Takes argument and evaluates it in the context of the shell to allow commands + * to be escaped to the outer shell. Used primarily for development purposes. + * @see DebugCommand.commands + * @param {string} str The expression to be evaluated + * @return 0 for handled internally + */ +DebugCommand.parseShell_ = function(str) { + print(eval(str)); + return 0; +} + +DebugCommand.parseShellDebug_ = function(str) { + shell_.debug = !shell_.debug; + if (shell_.debug) { + print("shell debugging enabled"); + } else { + print("shell debugging disabled"); + } + return 0; +} + +/** + * Parses a user-entered command string. + * @param {string} str The arguments to be parsed. + */ +DebugCommand.prototype.parseArgs_ = function(str) { + if (str.length) + str = DebugCommand.trim(str); + var cmd = DebugCommand.commands[this.user_command]; + if (cmd) { + var parse = cmd['parse']; + if (parse == undefined) { + print('>>>can\'t find parse func for ' + this.user_command); + this.type = "error"; + } else { + var ret = parse.call(this, str); + if (ret > 0) { + // Command gererated a debugger request. + this.type = "request"; + } else if (ret == 0) { + // Command handeled internally. + this.type = "handled"; + } else if (ret < 0) { + // Command error. + this.type = "handled"; + DebugCommand.help(this.user_command); + } + } + } else { + this.type = "handled"; + print('unknown command: ' + this.user_command); + DebugCommand.help(); + } +}; + +/** + * Displays command help or all help. + * @param {string} opt_str Which command to print help for. + */ +DebugCommand.help = function(opt_str) { + if (opt_str) { + var cmd = DebugCommand.commands[opt_str]; + var usage = cmd.usage; + print('usage: ' + usage); + // Print additional details for the command. + if (cmd.help) { + print(cmd.help); + } + } else { + if (shell_.running) { + print('Status: page is running'); + } else { + print('Status: page is paused'); + } + print('Available commands:'); + for (var key in DebugCommand.commands) { + var cmd = DebugCommand.commands[key]; + if (!cmd['hidden'] && (!shell_.running || cmd['while_running'])) { + var usage = cmd.usage; + print(' ' + usage); + } + } + } +}; + +/** + * Valid commands, their argument parser and their associated usage text. + */ +DebugCommand.commands = { + 'args': { 'parse': DebugCommand.prototype.parseArgsAndLocals_, + 'usage': 'args', + 'help': 'summarize the arguments to the current function.', + 'response': DebugCommand.responseArgs_ }, + 'break': { 'parse': DebugCommand.prototype.parseBreak_, + 'response': DebugCommand.responseBreak_, + 'usage': 'break [location] <condition>', + 'help': 'location is one of <function> | <script:function> | <script:line> | <script:line:pos>', + 'while_running': true }, + 'break_info': { 'parse': DebugCommand.prototype.parseBreakInfo_, + 'usage': 'break_info [breakpoint #]', + 'help': 'list the current breakpoints, or the details on a single one', + 'while_running': true }, + 'backtrace': { 'parse': DebugCommand.prototype.parseBacktrace_, + 'response': DebugCommand.responseBacktrace_, + 'usage': 'backtrace [from frame #] [to frame #]' }, + 'clear': { 'parse': DebugCommand.prototype.parseClearCommand_, + 'response': DebugCommand.responseClear_, + 'usage': 'clear <breakpoint #>', + 'while_running': true }, + 'continue': { 'parse': DebugCommand.prototype.parseContinueCommand_, + 'usage': 'continue' }, + 'dir': { 'parse': DebugCommand.prototype.parseDir_, + 'response': DebugCommand.responseDir_, + 'usage': 'dir <expression>', + 'while_running': true }, + 'frame': { 'parse': DebugCommand.prototype.parseFrame_, + 'response': DebugCommand.responseFrame_, + 'usage': 'frame <frame #>' }, + 'help': { 'parse': DebugCommand.parseHelp_, + 'usage': 'help [command]', + 'while_running': true }, + 'locals': { 'parse': DebugCommand.prototype.parseArgsAndLocals_, + 'usage': 'locals', + 'help': 'summarize the local variables for current frame', + 'response': DebugCommand.responseLocals_ }, + 'next': { 'parse': DebugCommand.prototype.parseNext_, + 'usage': 'next' } , + 'print': { 'parse': DebugCommand.prototype.parsePrint_, + 'response': DebugCommand.responsePrint_, + 'usage': 'print <expression>', + 'while_running': true }, + 'scripts': { 'parse': DebugCommand.prototype.parseScripts_, + 'response': DebugCommand.responseScripts_, + 'usage': 'scripts', + 'while_running': true }, + 'source': { 'parse': DebugCommand.prototype.parseSource_, + 'response': DebugCommand.responseSource_, + 'usage': 'source [from line] | [<from line> <num lines>]' }, + 'step': { 'parse': DebugCommand.prototype.parseStep_, + 'usage': 'step' }, + 'stepout': { 'parse': DebugCommand.prototype.parseStepOut_, + 'usage': 'stepout' }, + // local eval for debugging - remove this later + 'shell': { 'parse': DebugCommand.parseShell_, + 'usage': 'shell <expression>', + 'while_running': true, + 'hidden': true }, + 'shelldebug': { 'parse': DebugCommand.parseShellDebug_, + 'usage': 'shelldebug', + 'while_running': true, + 'hidden': true }, +}; + + +/** + * Debug shell using the new JSON protocol + * @param {Object} tab - which tab is to be debugged. This is an internal + * Chrome object. + * @constructor + */ +function DebugShell(tab) { + this.tab = tab; + this.tab.attach(); + this.ready = true; + this.running = true; + this.current_command = undefined; + this.pending_commands = []; + // The auto continue flag is used to indicate whether the JavaScript execution + // should automatically continue after a break event and the processing of + // pending commands. This is used to make it possible for the user to issue + // commands, e.g. setting break points, without making an explicit break. In + // this case the debugger will silently issue a forced break issue the command + // and silently continue afterwards. + this.auto_continue = false; + this.debug = false; + this.current_line = -1; + this.current_pos = -1; + this.current_frame = 0; + this.current_script = undefined; + this.scripts = []; + + // Mapping of breakpoints id --> info. + // Must use numeric keys. + this.breakpoints = []; +}; + +DebugShell.prototype.set_ready = function(ready) { + if (ready != this.ready) { + this.ready = ready; + ChromeNode.setDebuggerReady(this.ready); + } +}; + +DebugShell.prototype.set_running = function(running) { + if (running != this.running) { + this.running = running; + ChromeNode.setDebuggerBreak(!this.running); + } +}; + +/** + * Execute a constructed DebugCommand object if possible, otherwise pend. + * @param cmd {DebugCommand} - command to execute + */ +DebugShell.prototype.process_command = function(cmd) { + dprint("Running: " + (this.running ? "yes" : "no")); + + // The "break" commands needs to be handled seperatly + if (cmd.command == "break") { + if (this.running) { + // Schedule a break. + print("Stopping JavaScript execution..."); + this.tab.debugBreak(false); + } else { + print("JavaScript execution already stopped."); + } + return; + } + + // If page is running an break needs to be issued. + if (this.running) { + // Some requests are not valid when the page is running. + var cmd_info = DebugCommand.commands[cmd.user_command]; + if (!cmd_info['while_running']) { + print(cmd.user_command + " can only be run while paused"); + return; + } + + // Add the command as pending before scheduling a break. + this.pending_commands.push(cmd); + dprint("pending command: " + cmd.toJSONProtocol()); + + // Schedule a forced break and enable auto continue. + this.tab.debugBreak(true); + this.auto_continue = true; + this.set_ready(false); + return; + } + + // If waiting for a response add command as pending otherwise send the + // command. + if (this.current_command) { + this.pending_commands.push(cmd); + dprint("pending command: " + cmd.toJSONProtocol()); + } else { + this.current_command = cmd; + cmd.sendToDebugger(this.tab); + this.set_ready(false); + } +}; + +/** + * Handle a break event from the debugger. + * @param msg {ResponsePacket} - break_event protocol message to handle + */ +DebugShell.prototype.event_break = function(break_event) { + this.current_frame = 0; + this.set_running(false); + var body = break_event.body(); + if (body) { + this.current_script = body.script; + var loc = DebugCommand.getSourceLocation(body.script, + body.sourceLineText, body.sourceLine, body.invocationText); + var location = loc[0]; + var source = loc[1]; + this.current_line = loc[2]; + if (body.breakpoints) { + // Always disable auto continue if a real break point is hit. + this.auto_continue = false; + var breakpoints = body.breakpoints; + print("paused at breakpoint " + breakpoints.join(",") + ": " + + location); + for (var i = 0; i < breakpoints.length; i++) + this.didHitBreakpoint(parseInt(breakpoints[i])); + } else if (body.scriptData == "") { + print("paused"); + } else { + // step, stepout, next, "break" and a "debugger" line in the code + // are all treated the same (they're not really distinguishable anyway) + if (location != this.last_break_location) { + // We only print the location (function + script) when it changes, + // so as we step, you only see the source line when you transition + // to a new script and/or function. Also if auto continue is enables + // don't print the break location. + if (!this.auto_continue) + print(location); + } + } + // Print th current source line unless auto continue is enabled. + if (source && !this.auto_continue) + print(source); + this.last_break_location = location; + } + if (!this.auto_continue) + this.set_ready(true); +}; + +/** + * Handle an exception event from the debugger. + * @param msg {ResponsePacket} - exception_event protocol message to handle + */ +DebugShell.prototype.event_exception = function(exception_event) { + this.set_running(false); + this.set_ready(true); + var body = exception_event.body(); + if (body) { + if (body["uncaught"]) { + print("uncaught exception " + body["exception"].text); + } else { + print("paused at exception " + body["exception"].text); + } + } +}; + +DebugShell.prototype.matchScript = function(script_match, line) { + var script = null; + // In the v8 debugger, all scripts have a name, line offset and line count + // Script names are usually URLs which are a pain to have to type again and + // again, so we match the tail end of the script name. This makes it easy + // to type break foo.js:23 rather than + // http://www.foo.com/bar/baz/quux/test/foo.js:23. In addition to the tail + // of the name we also look at the lines the script cover. If there are + // several scripts with the same tail including the requested line we match + // the first one encountered. + // TODO(sgjesse) Find how to handle several matching scripts. + var candidate_scripts = []; + for (var i in this.scripts) { + if (this.scripts[i].name && + this.scripts[i].name.indexOf(script_match) >= 0) { + candidate_scripts.push(this.scripts[i]); + } + } + for (var i in candidate_scripts) { + var s = candidate_scripts[i]; + var from = s.lineOffset; + var to = from + s.lineCount; + if (from <= line && line < to) { + script = s; + break; + } + } + if (script) + return script; + else + return null; +} + +// The Chrome Subshell interface requires: +// prompt(), command(), response(), exit() and on_disconnect() + +/** + * Called by Chrome Shell to get a prompt string to display. + */ +DebugShell.prototype.prompt = function() { + if (this.current_command) + return ''; + if (!this.running) + return 'v8(paused)> '; + else + return 'v8(running)> '; +}; + +/** + * Called by Chrome Shell when command input has been received from the user. + */ +DebugShell.prototype.command = function(str) { + if (this.tab) { + str = DebugCommand.trim(str); + if (str.length) { + var cmd = new DebugCommand(str); + cmd.from_user = true; + if (cmd.type == "request") + this.process_command(cmd); + } + } else { + print(">>not connected to a tab"); + } +}; + +/** + * Called by Chrome Shell when a response to a previous command has been + * received. + * @param {Object} msg Message object. + */ +DebugShell.prototype.response = function(msg) { + dprint("received: " + (msg && msg.type)); + var response; + try { + response = new ProtocolPackage(msg); + } catch (error) { + print(error.toString(), str); + return; + } + if (response.type() == "event") { + ev = response.event(); + if (ev == "break") { + this.event_break(response); + } else if (ev == "exception") { + this.event_exception(response); + } + } else if (response.type() == "response") { + if (response.requestSeq() != undefined) { + if (!this.current_command || this.current_command.seq != response.requestSeq()){ + throw("received response to unknown command " + str); + } + } else { + // TODO(erikkay): should we reject these when they happen? + print(">>no request_seq in response " + str); + } + var cmd = DebugCommand.commands[this.current_command.user_command] + response.command = this.current_command; + this.current_command = null + this.set_running(response.running()); + if (!response.success()) { + print(response.message()); + } else { + var handler = cmd['response']; + if (handler != undefined) { + handler.call(this, response); + } + } + this.set_ready(true); + } + + // Process next pending command if any. + if (this.pending_commands.length) { + this.process_command(this.pending_commands.shift()); + } else if (this.auto_continue) { + // If no more pending commands and auto continue is active issue a continue command. + this.auto_continue = false; + this.process_command(new DebugCommand("continue")); + } +}; + +/** + * Called when a breakpoint has been set. + * @param {BreakpointInfo} info - details of breakpoint set. + */ +DebugShell.prototype.addedBreakpoint = function(info) { + print("set breakpoint #" + info.id); + this.breakpoints[info.id] = info; +} + +/** + * Called when a breakpoint has been cleared. + * @param {int} id - the breakpoint number that was cleared. + */ +DebugShell.prototype.clearedBreakpoint = function(id) { + assertIsNumberType(id, "clearedBreakpoint called with invalid id"); + + print("cleared breakpoint #" + id); + delete this.breakpoints[id]; +} + +/** + * Called when a breakpoint has been reached. + * @param {int} id - the breakpoint number that was hit. + */ +DebugShell.prototype.didHitBreakpoint = function(id) { + assertIsNumberType(id, "didHitBreakpoint called with invalid id"); + + var info = this.breakpoints[id]; + if (!info) + throw "Could not find breakpoint #" + id; + + info.hit_count ++; +} + +/** + * Print a summary of the specified breakpoints. + * + * @param {Array<BreakpointInfo>} breakpointsToPrint - List of breakpoints. The + * index is unused (id is determined from the info). + */ +DebugShell.printBreakpoints_ = function(breakpoints) { + // TODO(ericroman): this would look much nicer if we could output as an HTML + // table. I tried outputting as formatted text table, but this looks aweful + // once it triggers wrapping (which is very likely if the target is a script) + + // Output as a comma separated list of key=value + for (var i in breakpoints) { + var b = breakpoints[i]; + var props = ["id", "hit_count", "type", "target", "line", "position", + "condition"]; + var propertyList = []; + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + var val = b[prop]; + if (val != undefined) + propertyList.push(prop + "=" + val); + } + print(propertyList.join(", ")); + } +} + +/** + * Called by Chrome Shell when the outer shell is detaching from debugging + * this tab. + */ +DebugShell.prototype.exit = function() { + if (this.tab) { + this.tab.detach(); + this.tab = null; + } +}; + +/** + * Called by the Chrome Shell when the tab that the shell is debugging + * have attached. + */ +DebugShell.prototype.on_attach = function(title) { + if (!title) + title = "Untitled"; + print('attached to ' + title); + // on attach, we update our current script list + var cmd = new DebugCommand("scripts"); + cmd.from_user = false; + this.process_command(cmd); +}; + + +/** + * Called by the Chrome Shell when the tab that the shell is debugging + * went away. + */ +DebugShell.prototype.on_disconnect = function() { + print(">>lost connection to tab"); + this.tab = null; +}; + + +/** + * Protocol packages send from the debugger. + * @param {string} json - raw protocol packet as JSON string. + * @constructor + */ +function ProtocolPackage(msg) { + this.packet_ = msg; + this.refs_ = []; + if (this.packet_.refs) { + for (var i = 0; i < this.packet_.refs.length; i++) { + this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i]; + } + } +} + + +/** + * Get the packet type. + * @return {String} the packet type + */ +ProtocolPackage.prototype.type = function() { + return this.packet_.type; +} + + +/** + * Get the packet event. + * @return {Object} the packet event + */ +ProtocolPackage.prototype.event = function() { + return this.packet_.event; +} + + +/** + * Get the packet request sequence. + * @return {number} the packet request sequence + */ +ProtocolPackage.prototype.requestSeq = function() { + return this.packet_.request_seq; +} + + +/** + * Get the packet request sequence. + * @return {number} the packet request sequence + */ +ProtocolPackage.prototype.running = function() { + return this.packet_.running ? true : false; +} + + +ProtocolPackage.prototype.success = function() { + return this.packet_.success ? true : false; +} + + +ProtocolPackage.prototype.message = function() { + return this.packet_.message; +} + + +ProtocolPackage.prototype.body = function() { + return this.packet_.body; +} + + +ProtocolPackage.prototype.lookup = function(handle) { + return this.refs_[handle]; +} + + +/** + * Structure that holds the details about a breakpoint. + * @constructor + * + * @param {int} id - breakpoint number + * @param {string} type - "script" or "function" + * @param {string} target - either a function name, or script url + * @param {int} line - line number in the script, or undefined + * @param {int} position - column in the script, or undefined + * @param {string} condition - boolean expression, or undefined + */ +function BreakpointInfo(id, type, target, line, position, condition) { + this.id = id; + this.type = type; + this.target = target; + + if (line != undefined) + this.line = line; + if (position != undefined) + this.position = position; + if (condition != undefined) + this.condition = condition; + + this.hit_count = 0; + + // Check that the id is numeric, otherwise will run into problems later + assertIsNumberType(this.id, "id is not a number"); +} + +var shell_ = null; +DebugShell.initDebugShell = function(debuggerUI) { + if (!DebugShell.singleton) { + DebugShell.ui = debuggerUI; + DebugShell.singleton = new DebugShell(TabNode); + shell_ = DebugShell.singleton; + + // enable debug output + //shell_.debug = true; + } +}; + +/** + * Print debugging message when DebugShell's debug flag is true. + */ +function dprint(str) { + if (shell_ && shell_.debug) { + print(str); + } +}; + +/** + * Helper that throws error if x is not a number + * @param x {object} - object to test type of + * @param error_message {string} - error to throw on failure + */ +function assertIsNumberType(x, error_message) { + if (typeof x != "number") + throw error_message; +} + +////////////////////// migration staff ////////////////////////// +// This file was copied from chrome\browser\debugger\resources\debugger_shell.js + +function print(txt) { + var ui = DebugShell.ui; + if (ui) { + ui.appendText(txt); + } +} + +var TabNode = { + debugBreak: function(force) { + DebuggerIPC.sendMessage(["debugBreak", force]); + }, + attach: function() { + DebuggerIPC.sendMessage(["attach"]); + }, + detach: function() { + // TODO(yurys): send this from DebugHandler when it's being destroyed? + DebuggerIPC.sendMessage(["detach"]); + }, + sendToDebugger: function(str) { + DebuggerIPC.sendMessage(["sendToDebugger", str]); + } +}; + +var ChromeNode = { + setDebuggerReady: function(isReady) { + DebuggerIPC.sendMessage(["setDebuggerReady", isReady]); + }, + setDebuggerBreak: function(isBreak) { + var ui = DebugShell.ui; + if (ui) { + ui.setDebuggerBreak(isBreak); + } + DebuggerIPC.sendMessage(["setDebuggerBreak", isBreak]); + } +}; diff --git a/webkit/inspector/README b/webkit/inspector/README new file mode 100644 index 0000000..c4ccbe6 --- /dev/null +++ b/webkit/inspector/README @@ -0,0 +1,11 @@ +This directory contains Inspector front-end files that are currently forked from
+the implementation upstream.
+
+Our intent is to upstream or unfork these as soon as possible. For all intents
+and purposes, we consider the presence of this directory a ghastly hack that we
+all regret but have to live with to move forward.
+
+Please do not add more files to this directory as this will aggravate the pain
+and suffering already inflicted by this forked state. If you need to make
+changes to this directory, please consider spending your energy on upstreaming
+or unforking them instead.
diff --git a/webkit/inspector/debugger.css b/webkit/inspector/debugger.css new file mode 100644 index 0000000..ed9df2f --- /dev/null +++ b/webkit/inspector/debugger.css @@ -0,0 +1,35 @@ +/** + * Style for javascript debugger. See debugger.html. + */ + +html,body { + margin: 0px; + padding: 0px; + height: 100%; +} +#output { + font-family: monospace; + background-color: #ffffff; + min-height: 100%; +} +#outer { + height: 100%; + width: 100%; + white-space: pre-wrap; + padding: 0px 0px 24px 0px; +} +#command-line { + bottom: 0px; + /* not quite sure why this 5px is necessary */ + right: 5px; + left: 0px; + position: fixed; + padding: 0px; + margin: 0px; +} +#command-line-text { + height: 20px; + display: block; + width: 100%; + font-family: monospace; +} diff --git a/webkit/inspector/debugger.html b/webkit/inspector/debugger.html new file mode 100644 index 0000000..f6bb917 --- /dev/null +++ b/webkit/inspector/debugger.html @@ -0,0 +1,31 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html> +<!-- + The UI for the javascript debugger window. +--> + <head> + <title>JavaScript Debugger</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF8" /> + <link rel="stylesheet" href="debugger.css" type="text/css" /> + <script type="text/javascript" src="DebuggerConsole.js"></script> + <script type="text/javascript" src="DebuggerIPC.js"></script> + <script type="text/javascript" src="DebuggerShell.js"></script> + </head> + + <body onload="onLoad();"> + + <table id='outer'> + <tr> + <td valign='bottom' id='output'>Chrome JavaScript Debugger<br />Type 'help' for a list of commands.<br /></td> + </tr> + </table> + + <div id='command-line'> + <!-- TODO(erikkay) - use addEventListener instead --> + <input id='command-line-text' + type="text" /> + </div> + + </body> +</html> |