Bug 1033153 - Part 3: Display a Promise's internal state in the VariableView's properties. r=vporof
authorNick Fitzgerald <fitzgen@gmail.com>
Thu, 23 Oct 2014 15:53:00 +0200
changeset 212445 7ab817145c8d390bc3265adc7cbc2e9ea2bc1465
parent 212444 d4ed6c1353b50ab5bd27e84859453dfe9751ee88
child 212446 2548cc70c191af400eb452e7a219187a4e4bab24
push id27711
push usercbook@mozilla.com
push dateMon, 27 Oct 2014 14:56:47 +0000
treeherdermozilla-central@20408ad61ce5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvporof
bugs1033153
milestone36.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 1033153 - Part 3: Display a Promise's internal state in the VariableView's properties. r=vporof
browser/devtools/debugger/test/browser.ini
browser/devtools/debugger/test/browser_dbg_variables-view-06.js
browser/devtools/debugger/test/doc_promise.html
browser/devtools/shared/widgets/VariablesViewController.jsm
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -71,16 +71,17 @@ support-files =
   doc_minified_bogus_map.html
   doc_native-event-handler.html
   doc_no-page-sources.html
   doc_pause-exceptions.html
   doc_pretty-print.html
   doc_pretty-print-2.html
   doc_pretty-print-3.html
   doc_pretty-print-on-paused.html
+  doc_promise.html
   doc_random-javascript.html
   doc_recursion-stack.html
   doc_same-line-functions.html
   doc_scope-variable.html
   doc_scope-variable-2.html
   doc_scope-variable-3.html
   doc_scope-variable-4.html
   doc_script-switching-01.html
@@ -273,16 +274,17 @@ skip-if = os == "linux" || e10s # Bug 88
 [browser_dbg_tracing-06.js]
 [browser_dbg_tracing-07.js]
 [browser_dbg_tracing-08.js]
 [browser_dbg_variables-view-01.js]
 [browser_dbg_variables-view-02.js]
 [browser_dbg_variables-view-03.js]
 [browser_dbg_variables-view-04.js]
 [browser_dbg_variables-view-05.js]
+[browser_dbg_variables-view-06.js]
 [browser_dbg_variables-view-accessibility.js]
 [browser_dbg_variables-view-data.js]
 [browser_dbg_variables-view-edit-cancel.js]
 [browser_dbg_variables-view-edit-click.js]
 skip-if = (os == 'mac' || os == 'win') && (debug == false) # Bug 986166
 [browser_dbg_variables-view-edit-getset-01.js]
 [browser_dbg_variables-view-edit-getset-02.js]
 [browser_dbg_variables-view-edit-value.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-06.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that Promises get their internal state added as psuedo properties.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_promise.html";
+
+const test = Task.async(function* () {
+  const [tab, debuggee, panel] = yield initDebugger(TAB_URL);
+  yield ensureSourceIs(panel, "doc_promise.html", true);
+
+  const scopes = waitForCaretAndScopes(panel, 21);
+  executeSoon(debuggee.doPause);
+  yield scopes;
+
+  const variables = panel.panelWin.DebuggerView.Variables;
+  ok(variables, "Should get the variables view.");
+
+  const scope = [...variables][0];
+  ok(scope, "Should get the current function's scope.");
+
+  const promiseVariables = [...scope].filter(([name]) =>
+    ["p", "f", "r"].indexOf(name) !== -1);
+
+  is(promiseVariables.length, 3,
+     "Should have our 3 promise variables: p, f, r");
+
+  for (let [name, item] of promiseVariables) {
+    info("Expanding variable '" + name + "'");
+    let expanded = once(variables, "fetched");
+    item.expand();
+    yield expanded;
+
+    let foundState = false;
+    switch (name) {
+      case "p":
+        for (let [property, { value }] of item) {
+          if (property !== "<state>") {
+            isnot(property, "<value>",
+                  "A pending promise shouldn't have a value");
+            isnot(property, "<reason>",
+                  "A pending promise shouldn't have a reason");
+            continue;
+          }
+
+          foundState = true;
+          is(value, "pending", "The state should be pending.");
+        }
+        ok(foundState, "We should have found the <state> property.");
+        break;
+
+      case "f":
+        let foundValue = false;
+        for (let [property, value] of item) {
+          if (property === "<state>") {
+            foundState = true;
+            is(value.value, "fulfilled", "The state should be fulfilled.");
+          } else if (property === "<value>") {
+            foundValue = true;
+
+            let expanded = once(variables, "fetched");
+            value.expand();
+            yield expanded;
+
+            let expectedProps = new Map([["a", 1], ["b", 2], ["c", 3]]);
+            for (let [prop, val] of value) {
+              if (prop === "__proto__") {
+                continue;
+              }
+              ok(expectedProps.has(prop), "The property should be expected.");
+              is(val.value, expectedProps.get(prop), "The property value should be correct.");
+              expectedProps.delete(prop);
+            }
+            is(Object.keys(expectedProps).length, 0,
+               "Should have found all of the expected properties.");
+          } else {
+            isnot(property, "<reason>",
+                  "A fulfilled promise shouldn't have a reason");
+          }
+        }
+        ok(foundState, "We should have found the <state> property.");
+        ok(foundValue, "We should have found the <value> property.");
+        break;
+
+      case "r":
+        let foundReason = false;
+        for (let [property, value] of item) {
+          if (property === "<state>") {
+            foundState = true;
+            is(value.value, "rejected", "The state should be rejected.");
+          } else if (property === "<reason>") {
+            foundReason = true;
+
+            let expanded = once(variables, "fetched");
+            value.expand();
+            yield expanded;
+
+            let foundMessage = false;
+            for (let [prop, val] of value) {
+              if (prop !== "message") {
+                continue;
+              }
+              foundMessage = true;
+              is(val.value, "uh oh", "Should have the correct error message.");
+            }
+            ok(foundMessage, "Should have found the error's message");
+          } else {
+            isnot(property, "<value>",
+                  "A rejected promise shouldn't have a value");
+          }
+        }
+        ok(foundState, "We should have found the <state> property.");
+        break;
+    }
+  }
+
+  debugger;
+
+  resumeDebuggerThenCloseAndFinish(panel);
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_promise.html
@@ -0,0 +1,30 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Debugger + Promise test page</title>
+  </head>
+
+  <body>
+    <script>
+    window.pending   = new Promise(function () {});
+    window.fulfilled = Promise.resolve({ a: 1, b: 2, c: 3 });
+    window.rejected  = Promise.reject(new Error("uh oh"));
+
+    window.doPause = function () {
+      var p = window.pending;
+      var f = window.fulfilled;
+      var r = window.rejected;
+      debugger;
+    };
+
+    // Attach an error handler so that the logs don't have a warning about an
+    // unhandled, rejected promise.
+    window.rejected.then(null, function () {});
+    </script>
+  </body>
+
+</html>
--- a/browser/devtools/shared/widgets/VariablesViewController.jsm
+++ b/browser/devtools/shared/widgets/VariablesViewController.jsm
@@ -165,16 +165,26 @@ VariablesViewController.prototype = {
    * @param Scope aTarget
    *        The Scope where the properties will be placed into.
    * @param object aGrip
    *        The grip to use to populate the target.
    */
   _populateFromObject: function(aTarget, aGrip) {
     let deferred = promise.defer();
 
+    if (aGrip.class === "Promise" && aGrip.promiseState) {
+      const { state, value, reason } = aGrip.promiseState;
+      aTarget.addItem("<state>", { value: state });
+      if (state === "fulfilled") {
+        this.addExpander(aTarget.addItem("<value>", { value }), value);
+      } else if (state === "rejected") {
+        this.addExpander(aTarget.addItem("<reason>", { value: reason }), reason);
+      }
+    }
+
     let objectClient = this._getObjectClient(aGrip);
     objectClient.getPrototypeAndProperties(aResponse => {
       let { ownProperties, prototype } = aResponse;
       // 'safeGetterValues' is new and isn't necessary defined on old actors.
       let safeGetterValues = aResponse.safeGetterValues || {};
       let sortable = VariablesView.isSortable(aGrip.class);
 
       // Merge the safe getter values into one object such that we can use it