Bug 683503 - GCLI needs commands to control the debugger; r=jwalker,dcamp
authorPanos Astithas <past@mozilla.com>
Fri, 10 Feb 2012 09:46:04 +0200
changeset 86727 7a0c7a6339e089aadeb5a2f0fac87305a4f991f2
parent 86726 ed364b0b4c627e3c0c2b2839bd46ffef50c24d04
child 86728 c98d2cd079d5f289d683612c725d1bf5108b908b
push id102
push userMs2ger@gmail.com
push dateFri, 10 Feb 2012 20:38:19 +0000
reviewersjwalker, dcamp
bugs683503
milestone13.0a1
Bug 683503 - GCLI needs commands to control the debugger; r=jwalker,dcamp
browser/devtools/debugger/debugger-view.js
browser/devtools/webconsole/GcliCommands.jsm
browser/devtools/webconsole/test/Makefile.in
browser/devtools/webconsole/test/browser_gcli_break.html
browser/devtools/webconsole/test/browser_gcli_break.js
browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -1088,16 +1088,27 @@ DebuggerView.Scripts = {
 
     let script = this._scripts.appendItem(aScriptNameText || aUrl, aUrl);
     script.setUserData("sourceScript", aSource, null);
     this._scripts.selectedItem = script;
     return script;
   },
 
   /**
+   * Returns the list of URIs for scripts in the page.
+   */
+  scriptLocations: function DVS_scriptLocations() {
+    let locations = [];
+    for (let i = 0; i < this._scripts.itemCount; i++) {
+      locations.push(this._scripts.getItemAtIndex(i).value);
+    }
+    return locations;
+  },
+
+  /**
    * The cached click listener for the scripts container.
    */
   _onScriptsChange: null,
 
   /**
    * The cached scripts container.
    */
   _scripts: null,
--- a/browser/devtools/webconsole/GcliCommands.jsm
+++ b/browser/devtools/webconsole/GcliCommands.jsm
@@ -120,8 +120,144 @@ gcli.addCommand({
       manual: gcli.lookup("inspectNodeManual")
     }
   ],
   exec: function Command_inspect(args, context) {
     let document = context.environment.chromeDocument;
     document.defaultView.InspectorUI.openInspectorUI(args.node);
   }
 });
+
+let breakpoints = [];
+
+/**
+ * 'break' command
+ */
+gcli.addCommand({
+  name: "break",
+  description: gcli.lookup("breakDesc"),
+  manual: gcli.lookup("breakManual")
+});
+
+
+/**
+ * 'break list' command
+ */
+gcli.addCommand({
+  name: "break list",
+  description: gcli.lookup("breaklistDesc"),
+  returnType: "html",
+  exec: function(args, context) {
+    if (breakpoints.length === 0) {
+      return gcli.lookup("breaklistNone");
+    }
+
+    let reply = gcli.lookup("breaklistIntro");
+    reply += "<ol>";
+    breakpoints.forEach(function(breakpoint) {
+      let text = gcli.lookupFormat("breaklistLineEntry",
+                                   [breakpoint.file, breakpoint.line]);
+      reply += "<li>" + text + "</li>";
+    });
+    reply += "</ol>";
+    return reply;
+  }
+});
+
+
+/**
+ * 'break add' command
+ */
+gcli.addCommand({
+  name: "break add",
+  description: gcli.lookup("breakaddDesc"),
+  manual: gcli.lookup("breakaddManual")
+});
+
+/**
+ * 'break add line' command
+ */
+gcli.addCommand({
+  name: "break add line",
+  description: gcli.lookup("breakaddlineDesc"),
+  params: [
+    {
+      name: "file",
+      type: {
+        name: "selection",
+        data: function() {
+          let win = HUDService.currentContext();
+          let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab);
+          let files = [];
+          if (dbg) {
+            let scriptsView = dbg.frame.contentWindow.DebuggerView.Scripts;
+            for each (let script in scriptsView.scriptLocations()) {
+              files.push(script);
+            }
+          }
+          return files;
+        }
+      },
+      description: gcli.lookup("breakaddlineFileDesc")
+    },
+    {
+      name: "line",
+      type: { name: "number", min: 1, step: 10 },
+      description: gcli.lookup("breakaddlineLineDesc")
+    }
+  ],
+  returnType: "html",
+  exec: function(args, context) {
+    args.type = "line";
+    let win = HUDService.currentContext();
+    let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab);
+    if (!dbg) {
+      return gcli.lookup("breakaddDebuggerStopped");
+    }
+    var promise = context.createPromise();
+    let position = { url: args.file, line: args.line };
+    dbg.activeThread.setBreakpoint(position, function(aResponse, aBpClient) {
+      if (aResponse.error) {
+        promise.resolve(gcli.lookupFormat("breakaddFailed",
+                        [ aResponse.error ]));
+        return;
+      }
+      args.client = aBpClient;
+      breakpoints.push(args);
+      promise.resolve(gcli.lookup("breakaddAdded"));
+    });
+    return promise;
+  }
+});
+
+
+/**
+ * 'break del' command
+ */
+gcli.addCommand({
+  name: "break del",
+  description: gcli.lookup("breakdelDesc"),
+  params: [
+    {
+      name: "breakid",
+      type: {
+        name: "number",
+        min: 0,
+        max: function() { return breakpoints.length - 1; }
+      },
+      description: gcli.lookup("breakdelBreakidDesc")
+    }
+  ],
+  returnType: "html",
+  exec: function(args, context) {
+    let breakpoint = breakpoints.splice(args.breakid, 1)[0];
+    var promise = context.createPromise();
+    try {
+      breakpoint.client.remove(function(aResponse) {
+                                 promise.resolve(gcli.lookup("breakdelRemoved"));
+                               });
+    } catch (ex) {
+      // If the debugger has been closed already, don't scare the user.
+      promise.resolve(gcli.lookup("breakdelRemoved"));
+    }
+    return promise;
+  }
+});
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -149,16 +149,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_gcli_inspect.js \
 	browser_gcli_integrate.js \
 	browser_gcli_require.js \
 	browser_gcli_web.js \
 	browser_webconsole_bug_658368_time_methods.js \
 	browser_webconsole_bug_622303_persistent_filters.js \
 	browser_webconsole_window_zombie.js \
 	browser_cached_messages.js \
+	browser_gcli_break.js \
 	head.js \
 	$(NULL)
 
 _BROWSER_TEST_PAGES = \
 	test-console.html \
 	test-network.html \
 	test-network-request.html \
 	test-mutation.html \
@@ -223,15 +224,16 @@ include $(topsrcdir)/config/rules.mk
 	test-bug-632275-getters.html \
 	test-bug-646025-console-file-location.html \
 	test-bug-678816-content.js \
 	test-file-location.js \
 	browser_gcli_inspect.html \
 	test-bug-658368-time-methods.html \
 	test-webconsole-error-observer.html \
 	test-for-of.html \
+	browser_gcli_break.html \
 	$(NULL)
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
 
 libs:: $(_BROWSER_TEST_PAGES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_gcli_break.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+	<head>
+		<title>Browser GCLI break command test</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+         http://creativecommons.org/publicdomain/zero/1.0/ -->
+    <script type="text/javascript">
+      function firstCall() {
+        eval("window.line0 = Error().lineNumber; secondCall();");
+      }
+      function secondCall() {
+        eval("debugger;");
+      }
+    </script>
+	</head>
+	<body>
+	</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_gcli_break.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// For more information on GCLI see:
+// - https://github.com/mozilla/gcli/blob/master/docs/index.md
+// - https://wiki.mozilla.org/DevTools/Features/GCLI
+
+// Tests that the break command works as it should
+
+let tempScope = {};
+Components.utils.import("resource:///modules/gcli.jsm", tempScope);
+let gcli = tempScope.gcli;
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/browser_gcli_break.html";
+registerCleanupFunction(function() {
+  gcliterm = undefined;
+  requisition = undefined;
+
+  Services.prefs.clearUserPref("devtools.gcli.enable");
+});
+
+function test() {
+  Services.prefs.setBoolPref("devtools.gcli.enable", true);
+  addTab(TEST_URI);
+  browser.addEventListener("DOMContentLoaded", onLoad, false);
+}
+
+let gcliterm;
+let requisition;
+
+function onLoad() {
+  browser.removeEventListener("DOMContentLoaded", onLoad, false);
+
+  try {
+    openConsole();
+
+    let hud = HUDService.getHudByWindow(content);
+    gcliterm = hud.gcliterm;
+    requisition = gcliterm.opts.requisition;
+
+    testSetup();
+    testCreateCommands();
+  }
+  catch (ex) {
+    ok(false, "Caught exception: " + ex)
+    gcli._internal.console.error("Test Failure", ex);
+    closeConsole();
+    finishTest();
+  }
+}
+
+function testSetup() {
+  ok(gcliterm, "We have a GCLI term");
+  ok(requisition, "We have a Requisition");
+}
+
+function testCreateCommands() {
+  type("brea");
+  is(gcliterm.completeNode.textContent, " break", "Completion for 'brea'");
+  is(requisition.getStatus().toString(), "ERROR", "brea is ERROR");
+
+  type("break");
+  is(requisition.getStatus().toString(), "ERROR", "break is ERROR");
+
+  type("break add");
+  is(requisition.getStatus().toString(), "ERROR", "break add is ERROR");
+
+  type("break add line");
+  is(requisition.getStatus().toString(), "ERROR", "break add line is ERROR");
+
+  let pane = DebuggerUI.toggleDebugger();
+  pane.onConnected = function test_onConnected(aPane) {
+    // Wait for the initial resume.
+    aPane.debuggerWindow.gClient.addOneTimeListener("resumed", function() {
+      delete aPane.onConnected;
+      aPane.debuggerWindow.gClient.activeThread.addOneTimeListener("scriptsadded", function() {
+        type("break add line " + TEST_URI + " " + content.wrappedJSObject.line0);
+        is(requisition.getStatus().toString(), "VALID", "break add line is VALID");
+        requisition.exec();
+
+        type("break list");
+        is(requisition.getStatus().toString(), "VALID", "break list is VALID");
+        requisition.exec();
+
+        aPane.debuggerWindow.gClient.activeThread.resume(function() {
+          type("break del 0");
+          is(requisition.getStatus().toString(), "VALID", "break del 0 is VALID");
+          requisition.exec();
+
+          closeConsole();
+          finishTest();
+        });
+      });
+      // Trigger newScript notifications using eval.
+      content.wrappedJSObject.firstCall();
+    });
+  }
+}
+
+function type(command) {
+  gcliterm.inputNode.value = command.slice(0, -1);
+  gcliterm.inputNode.focus();
+  EventUtils.synthesizeKey(command.slice(-1), {});
+}
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -51,12 +51,81 @@ inspectManual=Investigate the dimensions
 # when the user is using this command.
 inspectNodeDesc=CSS selector
 
 # LOCALIZATION NOTE (inspectNodeManual) A fuller description of the 'node'
 # parameter to the 'inspect' command, displayed when the user asks for help
 # on what it does.
 inspectNodeManual=A CSS selector for use with Document.querySelector which identifies a single element
 
+# LOCALIZATION NOTE (breakDesc) A very short string used to describe the
+# function of the break command.
+breakDesc=Manage breakpoints
+
+# LOCALIZATION NOTE (breakManual) A longer description describing the
+# set of commands that control breakpoints.
+breakManual=Commands to list, add and remove breakpoints
+
+# LOCALIZATION NOTE (breaklistDesc) A very short string used to describe the
+# function of the 'break list' command.
+breaklistDesc=Display known breakpoints
+
+# LOCALIZATION NOTE (breaklistLineEntry) Used in the output of the 'break list'
+# command to display a single line breakpoint.
+# %1$S=script URL, %2$S=line number
+breaklistLineEntry=Line breakpoint at %1$S:%2$S
+
+# LOCALIZATION NOTE (breaklistNone) Used in the output of the 'break list'
+# command to explain that the list is empty.
+breaklistNone=No breakpoints set
+
+# LOCALIZATION NOTE (breaklistIntro) Used in the output of the 'break list'
+# command to preface the list contents.
+breaklistIntro=The following breakpoints are set:
+
+# LOCALIZATION NOTE (breakaddAdded) Used in the output of the 'break add'
+# command to explain that a breakpoint was added.
+breakaddAdded=Added breakpoint
+
+# LOCALIZATION NOTE (breakaddFailed) Used in the output of the 'break add'
+# command to explain that a breakpoint could not be added.
+breakaddFailed=Could not set breakpoint: %S
+
+# LOCALIZATION NOTE (breakaddDesc) A very short string used to describe the
+# function of the 'break add' command.
+breakaddDesc=Add a breakpoint
+
+# LOCALIZATION NOTE (breakaddManual) A longer description describing the
+# set of commands that are responsible for adding breakpoints.
+breakaddManual=Breakpoint types supported: line
+
+# LOCALIZATION NOTE (breakaddDebuggerStopped) Used in the output of the
+# 'break add' command to explain that the debugger must be opened first.
+breakaddDebuggerStopped=The debugger must be opened before setting breakpoints
+
+# LOCALIZATION NOTE (breakaddlineDesc) A very short string used to describe the
+# function of the 'break add line' command.
+breakaddlineDesc=Add a line breakpoint
+
+# LOCALIZATION NOTE (breakaddlineFileDesc) A very short string used to describe
+# the function of the file parameter in the 'break add line' command.
+breakaddlineFileDesc=JS file URI
+
+# LOCALIZATION NOTE (breakaddlineLineDesc) A very short string used to describe
+# the function of the line parameter in the 'break add line' command.
+breakaddlineLineDesc=Line number
+
+# LOCALIZATION NOTE (breakdelDesc) A very short string used to describe the
+# function of the 'break del' command.
+breakdelDesc=Remove a breakpoint
+
+# LOCALIZATION NOTE (breakdelBreakidDesc) A very short string used to describe
+# the function of the index parameter in the 'break del' command.
+breakdelBreakidDesc=Index of breakpoint
+
+# LOCALIZATION NOTE (breakdelRemoved) Used in the output of the 'break del'
+# command to explain that a breakpoint was removed.
+breakdelRemoved=Breakpoint removed
+
 # LOCALIZATION NOTE (consolecloseDesc) A very short description of the
 # 'console close' command. This string is designed to be shown in a menu
 # alongside the command name, which is why it should be as short as possible.
 consolecloseDesc=Close the console