Bug 575789: Implements helper $, 2519, clear, keys, values, inspect and pprint. r=sdwilsh a=beta6+
authorJulian Viereck <jviereck@mozilla.com>
Thu, 02 Sep 2010 14:30:45 -0700
changeset 51928 6160fe29dd370a5b13f339c8679504fdada84c86
parent 51927 b5538db7d5eb00c3e28184356680c87e1a426a29
child 51929 520233ed37c19cae6d5799c76058350bade5aea8
push idunknown
push userunknown
push dateunknown
reviewerssdwilsh, beta6
bugs575789
milestone2.0b6pre
Bug 575789: Implements helper $, 2519, clear, keys, values, inspect and pprint. r=sdwilsh a=beta6+
toolkit/components/console/hudservice/HUDService.jsm
toolkit/components/console/hudservice/PropertyPanel.jsm
toolkit/components/console/hudservice/tests/browser/browser_HUDServiceTestsAll.js
toolkit/components/console/hudservice/tests/browser/test-console.html
toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
--- a/toolkit/components/console/hudservice/HUDService.jsm
+++ b/toolkit/components/console/hudservice/HUDService.jsm
@@ -74,16 +74,21 @@ XPCOMUtils.defineLazyGetter(this, "Prope
   try {
     Cu.import("resource://gre/modules/PropertyPanel.jsm", obj);
   } catch (err) {
     Cu.reportError(err);
   }
   return obj.PropertyPanel;
 });
 
+XPCOMUtils.defineLazyGetter(this, "namesAndValuesOf", function () {
+  var obj = {};
+  Cu.import("resource://gre/modules/PropertyPanel.jsm", obj);
+  return obj.namesAndValuesOf;
+});
 
 function LogFactory(aMessagePrefix)
 {
   function log(aMessage) {
     var _msg = aMessagePrefix + " " + aMessage + "\n";
     dump(_msg);
   }
   return log;
@@ -3484,16 +3489,181 @@ function JSPropertyProvider(aScope, aInp
   };
 }
 
 //////////////////////////////////////////////////////////////////////////
 // JSTerm
 //////////////////////////////////////////////////////////////////////////
 
 /**
+ * JSTermHelper
+ *
+ * Defines a set of functions ("helper functions") that are available from the
+ * WebConsole but not from the webpage.
+ * A list of helper functions used by Firebug can be found here:
+ *   http://getfirebug.com/wiki/index.php/Command_Line_API
+ */
+function JSTermHelper(aJSTerm)
+{
+  return {
+    /**
+     * Returns the result of document.getElementById(aId).
+     *
+     * @param string aId
+     *        A string that is passed to window.document.getElementById.
+     * @returns nsIDOMNode or null
+     */
+    $: function JSTH_$(aId)
+    {
+      try {
+        return aJSTerm._window.document.getElementById(aId);
+      }
+      catch (ex) {
+        aJSTerm.console.error(ex.message);
+      }
+    },
+
+    /**
+     * Returns the result of document.querySelectorAll(aSelector).
+     *
+     * @param string aSelector
+     *        A string that is passed to window.document.querySelectorAll.
+     * @returns array of nsIDOMNode
+     */
+    $$: function JSTH_$$(aSelector)
+    {
+      try {
+        return aJSTerm._window.document.querySelectorAll(aSelector);
+      }
+      catch (ex) {
+        aJSTerm.console.error(ex.message);
+      }
+    },
+
+    /**
+     * Runs a xPath query and returns all matched nodes.
+     *
+     * @param string aXPath
+     *        xPath search query to execute.
+     * @param [optional] nsIDOMNode aContext
+     *        Context to run the xPath query on. Uses window.document if not set.
+     * @returns array of nsIDOMNode
+     */
+    $x: function JSTH_$x(aXPath, aContext)
+    {
+      let nodes = [];
+      let doc = aJSTerm._window.wrappedJSObject.document;
+      let aContext = aContext || doc;
+
+      try {
+        let results = doc.evaluate(aXPath, aContext, null,
+                                    Ci.nsIDOMXPathResult.ANY_TYPE, null);
+
+        let node;
+        while (node = results.iterateNext()) {
+          nodes.push(node);
+        }
+      }
+      catch (ex) {
+        aJSTerm.console.error(ex.message);
+      }
+
+      return nodes;
+    },
+
+    /**
+     * Clears the output of the JSTerm.
+     */
+    clear: function JSTH_clear()
+    {
+      aJSTerm.clearOutput();
+    },
+
+    /**
+     * Returns the result of Object.keys(aObject).
+     *
+     * @param object aObject
+     *        Object to return the property names from.
+     * @returns array of string
+     */
+    keys: function JSTH_keys(aObject)
+    {
+      try {
+        return Object.keys(XPCNativeWrapper.unwrap(aObject));
+      }
+      catch (ex) {
+        aJSTerm.console.error(ex.message);
+      }
+    },
+
+    /**
+     * Returns the values of all properties on aObject.
+     *
+     * @param object aObject
+     *        Object to display the values from.
+     * @returns array of string
+     */
+    values: function JSTH_values(aObject)
+    {
+      let arrValues = [];
+      let obj = XPCNativeWrapper.unwrap(aObject);
+
+      try {
+        for (let prop in obj) {
+          arrValues.push(obj[prop]);
+        }
+      }
+      catch (ex) {
+        aJSTerm.console.error(ex.message);
+      }
+      return arrValues;
+    },
+
+    /**
+     * Inspects the passed aObject. This is done by opening the PropertyPanel.
+     *
+     * @param object aObject
+     *        Object to inspect.
+     * @returns void
+     */
+    inspect: function JSTH_inspect(aObject)
+    {
+      let obj = XPCNativeWrapper.unwrap(aObject);
+      aJSTerm.openPropertyPanel(null, obj);
+    },
+
+    /**
+     * Prints aObject to the output.
+     *
+     * @param object aObject
+     *        Object to print to the output.
+     * @returns void
+     */
+    pprint: function JSTH_pprint(aObject)
+    {
+      if (aObject === null || aObject === undefined || aObject === true || aObject === false) {
+        aJSTerm.console.error(HUDService.getStr("helperFuncUnsupportedTypeError"));
+        return;
+      }
+      let output = [];
+      if (typeof aObject != "string") {
+        aObject = XPCNativeWrapper.unwrap(aObject);
+      }
+      let pairs = namesAndValuesOf(aObject);
+
+      pairs.forEach(function(pair) {
+        output.push("  " + pair.display);
+      });
+
+      aJSTerm.writeOutput(output.join("\n"));
+    }
+  }
+}
+
+/**
  * JSTerm
  *
  * JavaScript Terminal: creates input nodes for console code interpretation
  * and 'JS Workspaces'
  */
 
 /**
  * Create a JSTerminal or attach a JSTerm input node to an existing output node
@@ -3571,16 +3741,17 @@ JSTerm.prototype = {
   createSandbox: function JST_setupSandbox()
   {
     // create a JS Sandbox out of this.context
     this._window.wrappedJSObject.jsterm = {};
     this.console = this._window.wrappedJSObject.console;
     this.sandbox = new Cu.Sandbox(this._window);
     this.sandbox.window = this._window;
     this.sandbox.console = this.console;
+    this.sandbox.__helperFunctions__ = JSTermHelper(this);
     this.sandbox.__proto__ = this._window.wrappedJSObject;
   },
 
   get _window()
   {
     return this.context.get().QueryInterface(Ci.nsIDOMWindowInternal);
   },
   /**
@@ -3589,17 +3760,17 @@ JSTerm.prototype = {
    *
    * @param string aString
    *        String to evaluate in the sandbox.
    * @returns something
    *          The result of the evaluation.
    */
   evalInSandbox: function JST_evalInSandbox(aString)
   {
-    let execStr = "with(window) {" + aString + "}";
+    let execStr = "with(__helperFunctions__) { with(window) {" + aString + "} }";
     return Cu.evalInSandbox(execStr,  this.sandbox, "default", "HUD Console", 1);
   },
 
 
   execute: function JST_execute(aExecuteString)
   {
     // attempt to execute the content of the inputNode
     aExecuteString = aExecuteString || this.inputNode.value;
--- a/toolkit/components/console/hudservice/PropertyPanel.jsm
+++ b/toolkit/components/console/hudservice/PropertyPanel.jsm
@@ -39,17 +39,17 @@
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
-var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];
+var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView", "namesAndValuesOf"];
 
 ///////////////////////////////////////////////////////////////////////////
 //// Helper for PropertyTreeView
 
 const TYPE_OBJECT = 0, TYPE_FUNCTION = 1, TYPE_ARRAY = 2, TYPE_OTHER = 3;
 
 /**
  * Figures out the type of aObject and the string to display in the tree.
--- a/toolkit/components/console/hudservice/tests/browser/browser_HUDServiceTestsAll.js
+++ b/toolkit/components/console/hudservice/tests/browser/browser_HUDServiceTestsAll.js
@@ -1044,16 +1044,61 @@ function testExecutionScope()
 
   is(/location;/.test(outputChildren[1].childNodes[0].nodeValue), true,
     "'location;' written to output");
 
   isnot(outputChildren[2].childNodes[0].textContent.indexOf(TEST_URI), -1,
     "command was executed in the window scope");
 }
 
+function testJSTermHelper()
+{
+  content.location.href = TEST_URI;
+
+  let HUD = HUDService.hudWeakReferences[hudId].get();
+  let jsterm = HUD.jsterm;
+
+  jsterm.clearOutput();
+  jsterm.execute("'id=' + $('header').getAttribute('id')");
+  let group = jsterm.outputNode.querySelector(".hud-group");
+  is(group.childNodes[2].textContent, "id=header", "$() worked");
+
+  jsterm.clearOutput();
+  jsterm.execute("headerQuery = $$('h1')");
+  jsterm.execute("'length=' + headerQuery.length");
+  let group = jsterm.outputNode.querySelector(".hud-group");
+  is(group.childNodes[4].textContent, "length=1", "$$() worked");
+
+  jsterm.clearOutput();
+  jsterm.execute("xpathQuery = $x('.//*', document.body);");
+  jsterm.execute("'headerFound='  + (xpathQuery[0] == headerQuery[0])");
+  let group = jsterm.outputNode.querySelector(".hud-group");
+  is(group.childNodes[4].textContent, "headerFound=true", "$x() worked");
+
+  // no jsterm.clearOutput() here as we clear the output using the clear() fn.
+  jsterm.execute("clear()");
+  let group = jsterm.outputNode.querySelector(".hud-group");
+  is(group.childNodes[1].textContent, "undefined", "clear() worked");
+
+  jsterm.clearOutput();
+  jsterm.execute("'keysResult=' + (keys({b:1})[0] == 'b')");
+  let group = jsterm.outputNode.querySelector(".hud-group");
+  is(group.childNodes[2].textContent, "keysResult=true", "keys() worked");
+
+  jsterm.clearOutput();
+  jsterm.execute("'valuesResult=' + (values({b:1})[0] == 1)");
+  let group = jsterm.outputNode.querySelector(".hud-group");
+  is(group.childNodes[2].textContent, "valuesResult=true", "values() worked");
+
+  jsterm.clearOutput();
+  jsterm.execute("pprint({b:2, a:1})");
+  let group = jsterm.outputNode.querySelector(".hud-group");
+  is(group.childNodes[2].textContent, "  a: 1\n  b: 2", "pprint() worked");
+}
+
 function testPropertyPanel()
 {
   var HUD = HUDService.hudWeakReferences[hudId].get();
   var jsterm = HUD.jsterm;
 
   let propPanel = jsterm.openPropertyPanel("Test", [
     1,
     /abc/,
@@ -1378,16 +1423,17 @@ function test() {
       testGroups();
       testNullUndefinedOutput();
       testJSInputAndOutputStyling();
       testExecutionScope();
       testCompletion();
       testPropertyProvider();
       testJSInputExpand();
       testPropertyPanel();
+      testJSTermHelper();
 
       // NOTE: Put any sync test above this comment.
       //
       // There is a set of async tests that have to run one after another.
       // Currently, when one test is done the next one is called from within the
       // test function until testEnd() is called that terminates the test.
       testNet();
     });
--- a/toolkit/components/console/hudservice/tests/browser/test-console.html
+++ b/toolkit/components/console/hudservice/tests/browser/test-console.html
@@ -11,12 +11,12 @@
       }
       console.info("INLINE SCRIPT:");
       test();
       console.warn("I'm warning you, he will eat up all yr bacon.");
       console.error("Error Message");
     </script>
   </head>
   <body>
-    <h1>Heads Up Display Demo</h1>
+    <h1 id="header">Heads Up Display Demo</h1>
     <button onclick="test();">Log stuff about Dolske</button>
   </body>
 </html>
--- a/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
+++ b/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
@@ -58,16 +58,17 @@ jsPropertyTitle=Object Inspector
 # Example: The user executed `window.document` in the WebConsole. The `document`
 # object is written to the output. If the user clicks on the `document` output
 # in the console, a PropertyPanel will show up. The title of the PropertyPanel
 # is set to `Inspect: window.document` because the clicked `document` object was
 # evaluated based on the `window.document` string.
 jsPropertyInspectTitle=Inspect: %S
 copyCmd.label=Copy
 copyCmd.accesskey=C
+helperFuncUnsupportedTypeError=Can't call pprint on this type of object.
 # LOCALIZATION NOTE (networkUrlWithStatus):
 #
 # When the HTTP request is started only the URL of the request is printed to the
 # WebConsole. As the response status of the HTTP request arrives, the URL string
 # is replaced by this string (the response status can look like `HTTP/1.1 200 OK`).
 # The bracket is not closed to mark that this request is not done by now. As the
 # request is finished (the HTTP connection is closed) this string is replaced
 # by `networkUrlWithStatusAndDuration` which has a closing the braket.