Bug 792063 - Add $_ console shortcut to return the previous command result;r=past
authorBrian Grinstead <bgrinstead@mozilla.com>
Fri, 27 Mar 2015 07:28:19 -0700
changeset 266455 697c11bd51f6ad213e375efa2e1fd2c253f98544
parent 266454 ef364246350a5f0af2b57fcfac7fb23188e8384e
child 266456 9630284e15e6ff436a1ec2b0a9c2e884eac69d04
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspast
bugs792063
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 792063 - Add $_ console shortcut to return the previous command result;r=past
toolkit/devtools/server/actors/webconsole.js
toolkit/devtools/webconsole/test/chrome.ini
toolkit/devtools/webconsole/test/common.js
toolkit/devtools/webconsole/test/test_jsterm_last_result.html
toolkit/devtools/webconsole/utils.js
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -66,16 +66,17 @@ function WebConsoleActor(aConnection, aP
 
   this._prefs = {};
 
   this.dbg = this.parentActor.makeDebugger();
 
   this._netEvents = new Map();
   this._gripDepth = 0;
   this._listeners = new Set();
+  this._lastConsoleInputEvaluation = undefined;
 
   this._onWillNavigate = this._onWillNavigate.bind(this);
   this._onChangedToplevelDocument = this._onChangedToplevelDocument.bind(this);
   events.on(this.parentActor, "changed-toplevel-document", this._onChangedToplevelDocument);
   this._onObserverNotification = this._onObserverNotification.bind(this);
   if (this.parentActor.isRootActor) {
     Services.obs.addObserver(this._onObserverNotification,
                              "last-pb-context-exited", false);
@@ -353,16 +354,17 @@ WebConsoleActor.prototype =
     this.conn.removeActorPool(this._actorPool);
     if (this.parentActor.isRootActor) {
       Services.obs.removeObserver(this._onObserverNotification,
                                   "last-pb-context-exited");
     }
     this._actorPool = null;
 
     this._jstermHelpersCache = null;
+    this._lastConsoleInputEvaluation = null;
     this._evalWindow = null;
     this._netEvents.clear();
     this.dbg.enabled = false;
     this.dbg = null;
     this.conn = null;
   },
 
   /**
@@ -498,16 +500,27 @@ WebConsoleActor.prototype =
    * @param object aActor
    *        The actor instance you want to release.
    */
   releaseActor: function WCA_releaseActor(aActor)
   {
     this._actorPool.removeActor(aActor.actorID);
   },
 
+  /**
+   * Returns the latest web console input evaluation.
+   * This is undefined if no evaluations have been completed.
+   *
+   * @return object
+   */
+  getLastConsoleInputEvaluation: function WCU_getLastConsoleInputEvaluation()
+  {
+    return this._lastConsoleInputEvaluation;
+  },
+
   //////////////////
   // Request handlers for known packet types.
   //////////////////
 
   /**
    * Handler for the "startListeners" request.
    *
    * @param object aRequest
@@ -811,16 +824,18 @@ WebConsoleActor.prototype =
     // the console should remain functional.
     let resultGrip;
     try {
       resultGrip = this.createValueGrip(result);
     } catch (e) {
       errorMessage = e;
     }
 
+    this._lastConsoleInputEvaluation = result;
+
     return {
       from: this.actorID,
       input: input,
       result: resultGrip,
       timestamp: timestamp,
       exception: errorGrip,
       exceptionMessage: this._createStringGrip(errorMessage),
       helperResult: helperResult,
--- a/toolkit/devtools/webconsole/test/chrome.ini
+++ b/toolkit/devtools/webconsole/test/chrome.ini
@@ -10,20 +10,21 @@ support-files =
 [test_basics.html]
 [test_bug819670_getter_throws.html]
 [test_cached_messages.html]
 [test_consoleapi.html]
 [test_consoleapi_innerID.html]
 [test_file_uri.html]
 [test_reflow.html]
 [test_jsterm.html]
+[test_jsterm_cd_iframe.html]
+[test_jsterm_last_result.html]
 [test_network_get.html]
 [test_network_longstring.html]
 [test_network_post.html]
 [test_network_security-hpkp.html]
 [test_network_security-hsts.html]
 [test_nsiconsolemessage.html]
 [test_object_actor.html]
 [test_object_actor_native_getters.html]
 [test_object_actor_native_getters_lenient_this.html]
 [test_page_errors.html]
 [test_throw.html]
-[test_jsterm_cd_iframe.html]
--- a/toolkit/devtools/webconsole/test/common.js
+++ b/toolkit/devtools/webconsole/test/common.js
@@ -3,16 +3,20 @@
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 Cu.import("resource://gre/modules/Services.jsm");
+const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+
+// This gives logging to stdout for tests
+var {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 
 let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
 let WebConsoleUtils = devtools.require("devtools/toolkit/webconsole/utils").Utils;
 
 let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
                           .getService(Ci.nsIConsoleAPIStorage);
 
 let {ConsoleServiceListener, ConsoleAPIListener} =
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_jsterm_last_result.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for the $_ getter</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript;version=1.8" src="common.js"></script>
+  <!-- Any copyright is dedicated to the Public Domain.
+     - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the $_ getter</p>
+
+<iframe id="content-iframe" src="http://example.com/chrome/toolkit/devtools/webconsole/test/sandboxed_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+let gState;
+
+function evaluateJS(input, callback) {
+  return new Promise((resolve, reject) => {
+    gState.client.evaluateJSAsync(input, response => {
+      if (callback) {
+        callback(response);
+      }
+      resolve(response);
+    });
+  });
+}
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+  attachConsole([], state => {
+    gState = state;
+    let tests = [checkUndefinedResult,checkAdditionResult,checkObjectResult];
+    runTests(tests, testEnd);
+  }, true);
+}
+
+let checkUndefinedResult = Task.async(function*() {
+  info ("$_ returns undefined if nothing has evaluated yet");
+  let response = yield evaluateJS("$_");
+  basicResultCheck(response, "$_", undefined);
+  nextTest();
+});
+
+let checkAdditionResult = Task.async(function*() {
+  info ("$_ returns last value and performs basic arithmetic");
+  let response = yield evaluateJS("2+2");
+  basicResultCheck(response, "2+2", 4);
+
+  response = yield evaluateJS("$_");
+  basicResultCheck(response, "$_", 4);
+
+  response = yield evaluateJS("$_ + 2");
+  basicResultCheck(response, "$_ + 2", 6);
+
+  response = yield evaluateJS("$_ + 4");
+  basicResultCheck(response, "$_ + 4", 10);
+
+  nextTest();
+});
+
+let checkObjectResult = Task.async(function*() {
+  info ("$_ has correct references to objects");
+
+  let response = yield evaluateJS("var foo = {bar:1}; foo;");
+  basicResultCheck(response, "var foo = {bar:1}; foo;", {
+    type: "object",
+    class: "Object",
+    actor: /[a-z]/,
+  });
+  checkObject(response.result.preview.ownProperties, {
+    bar: {
+      value: 1
+    }
+  });
+
+  response = yield evaluateJS("$_");
+  basicResultCheck(response, "$_", {
+    type: "object",
+    class: "Object",
+    actor: /[a-z]/,
+  });
+  checkObject(response.result.preview.ownProperties, {
+    bar: {
+      value: 1
+    }
+  });
+
+  top.foo.bar = 2;
+
+  response = yield evaluateJS("$_");
+  basicResultCheck(response, "$_", {
+    type: "object",
+    class: "Object",
+    actor: /[a-z]/,
+  });
+  checkObject(response.result.preview.ownProperties, {
+    bar: {
+      value: 2
+    }
+  });
+
+  nextTest();
+});
+
+function basicResultCheck(response, input, output) {
+  checkObject(response, {
+    from: gState.actor,
+    input: input,
+    result: output,
+  });
+  ok(!response.exception, "no eval exception");
+  ok(!response.helperResult, "no helper result");
+}
+
+function testEnd()
+{
+  closeDebugger(gState, function() {
+    gState = null;
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -1545,16 +1545,30 @@ function JSTermHelpers(aOwner)
    *         Returns the result of document.querySelectorAll(aSelector).
    */
   aOwner.sandbox.$$ = function JSTH_$$(aSelector)
   {
     return aOwner.window.document.querySelectorAll(aSelector);
   };
 
   /**
+   * Returns the result of the last console input evaluation
+   *
+   * @return object|undefined
+   * Returns last console evaluation or undefined
+   */
+  Object.defineProperty(aOwner.sandbox, "$_", {
+    get: function() {
+      return aOwner.consoleActor.getLastConsoleInputEvaluation();
+    },
+    enumerable: true,
+    configurable: true
+  });
+
+  /**
    * Runs an 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.
    * @return array of nsIDOMNode
    */