Bug 1196319 - Part 2: Implement getRejectionStack method in ObjectClient r=fitzgen
authorGabriel Luong <gabriel.luong@gmail.com>
Thu, 20 Aug 2015 11:57:36 -0700
changeset 291203 99c90b779260ad4dee932cdf4f3dcafadd0cd286
parent 291202 b1a1a5b6acb7a48a71602cb089defc9c9a0ef9c1
child 291204 c4c4a57e0d742b5a7fc49ffc09141956657e96ca
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfitzgen
bugs1196319
milestone43.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 1196319 - Part 2: Implement getRejectionStack method in ObjectClient r=fitzgen
browser/devtools/debugger/test/browser.ini
browser/devtools/debugger/test/browser_dbg_promises-rejection-stack.js
browser/devtools/debugger/test/doc_promise-get-rejection-stack.html
toolkit/devtools/client/main.js
toolkit/devtools/server/actors/object.js
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -83,16 +83,17 @@ support-files =
   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-get-allocation-stack.html
   doc_promise-get-fulfillment-stack.html
+  doc_promise-get-rejection-stack.html
   doc_promise.html
   doc_random-javascript.html
   doc_recursion-stack.html
   doc_scope-variable.html
   doc_scope-variable-2.html
   doc_scope-variable-3.html
   doc_scope-variable-4.html
   doc_script-eval.html
@@ -347,16 +348,18 @@ skip-if = e10s && debug
 [browser_dbg_progress-listener-bug.js]
 skip-if = e10s && debug
 [browser_dbg_promises-allocation-stack.js]
 skip-if = e10s && debug
 [browser_dbg_promises-chrome-allocation-stack.js]
 skip-if = true # Bug 1177730
 [browser_dbg_promises-fulfillment-stack.js]
 skip-if = e10s && debug
+[browser_dbg_promises-rejection-stack.js]
+skip-if = e10s && debug
 [browser_dbg_reload-preferred-script-01.js]
 skip-if = e10s && debug
 [browser_dbg_reload-preferred-script-02.js]
 skip-if = e10s && debug
 [browser_dbg_reload-preferred-script-03.js]
 skip-if = e10s && debug
 [browser_dbg_reload-same-script.js]
 skip-if = e10s && debug
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_promises-rejection-stack.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get a stack to a promise's rejection point.
+ */
+
+"use strict";
+
+const TAB_URL = EXAMPLE_URL + "doc_promise-get-rejection-stack.html";
+const { PromisesFront } = require("devtools/server/actors/promises");
+let events = require("sdk/event/core");
+
+const TEST_DATA = [
+  {
+    functionDisplayName: "returnPromise/<",
+    line: 19,
+    column: 47
+  },
+  {
+    functionDisplayName: "returnPromise",
+    line: 19,
+    column: 14
+  },
+  {
+    functionDisplayName: "makePromise",
+    line: 14,
+    column: 15
+  },
+];
+
+function test() {
+  Task.spawn(function* () {
+    DebuggerServer.init();
+    DebuggerServer.addBrowserActors();
+
+    const [ tab,, panel ] = yield initDebugger(TAB_URL);
+
+    let client = new DebuggerClient(DebuggerServer.connectPipe());
+    yield connect(client);
+
+    let { tabs } = yield listTabs(client);
+    let targetTab = findTab(tabs, TAB_URL);
+    yield attachTab(client, targetTab);
+
+    yield testGetRejectionStack(client, targetTab, tab);
+
+    yield close(client);
+    yield closeDebuggerAndFinish(panel);
+  }).then(null, error => {
+    ok(false, "Got an error: " + error.message + "\n" + error.stack);
+  });
+}
+
+function* testGetRejectionStack(client, form, tab) {
+  let front = PromisesFront(client, form);
+
+  yield front.attach();
+  yield front.listPromises();
+
+  // Get the grip for promise p
+  let onNewPromise = new Promise(resolve => {
+    events.on(front, "new-promises", promises => {
+      for (let p of promises) {
+        if (p.preview.ownProperties.name &&
+            p.preview.ownProperties.name.value === "p") {
+          resolve(p);
+        }
+      }
+    });
+  });
+
+  callInTab(tab, "makePromise");
+
+  let grip = yield onNewPromise;
+  ok(grip, "Found our promise p");
+
+  let objectClient = new ObjectClient(client, grip);
+  ok(objectClient, "Got Object Client");
+
+  yield new Promise(resolve => {
+    objectClient.getPromiseRejectionStack(response => {
+      ok(response.rejectionStack.length, "Got promise allocation stack.");
+
+      for (let i = 0; i < TEST_DATA.length; i++) {
+        let stack = response.rejectionStack[i];
+        let data = TEST_DATA[i];
+        is(stack.source.url, TAB_URL, "Got correct source URL.");
+        is(stack.functionDisplayName, data.functionDisplayName,
+           "Got correct function display name.");
+        is(stack.line, data.line, "Got correct stack line number.");
+        is(stack.column, data.column, "Got correct stack column number.");
+      }
+
+      resolve();
+    });
+  });
+
+  yield front.detach();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_promise-get-rejection-stack.html
@@ -0,0 +1,24 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Promise test page</title>
+  </head>
+
+  <body>
+    <script type="text/javascript">
+    function makePromise() {
+      var p = returnPromise();
+      p.name = "p";
+    }
+
+    function returnPromise() {
+      return new Promise((resolve, reject) => reject("hello"));
+    }
+    </script>
+  </body>
+
+</html>
--- a/toolkit/devtools/client/main.js
+++ b/toolkit/devtools/client/main.js
@@ -2429,16 +2429,31 @@ ObjectClient.prototype = {
   }, {
     before: function(packet) {
       if (this._grip.class !== "Promise") {
         throw new Error("getPromiseFulfillmentStack is only valid for " +
           "promise grips.");
       }
       return packet;
     }
+  }),
+
+  /**
+   * Request the stack to the promise's rejection point.
+   */
+  getPromiseRejectionStack: DebuggerClient.requester({
+    type: "rejectionStack"
+  }, {
+    before: function(packet) {
+      if (this._grip.class !== "Promise") {
+        throw new Error("getPromiseRejectionStack is only valid for " +
+          "promise grips.");
+      }
+      return packet;
+    }
   })
 };
 
 /**
  * A PropertyIteratorClient provides a way to access to property names and
  * values of an object efficiently, slice by slice.
  * Note that the properties can be sorted in the backend,
  * this is controled while creating the PropertyIteratorClient
--- a/toolkit/devtools/server/actors/object.js
+++ b/toolkit/devtools/server/actors/object.js
@@ -602,16 +602,46 @@ ObjectActor.prototype = {
     }
 
     return Promise.all(fulfillmentStacks).then(stacks => {
       return { fulfillmentStack: stacks };
     });
   },
 
   /**
+   * Handle a protocol request to get the rejection stack of a promise.
+   */
+  onRejectionStack: function() {
+    if (this.obj.class != "Promise") {
+      return { error: "objectNotPromise",
+               message: "'rejectionStack' request is only valid for " +
+                        "object grips with a 'Promise' class." };
+    }
+
+    let rawPromise = this.obj.unsafeDereference();
+    let stack = PromiseDebugging.getRejectionStack(rawPromise);
+    let rejectionStacks = [];
+
+    while (stack) {
+      if (stack.source) {
+        let source = this._getSourceOriginalLocation(stack);
+
+        if (source) {
+          rejectionStacks.push(source);
+        }
+      }
+      stack = stack.parent;
+    }
+
+    return Promise.all(rejectionStacks).then(stacks => {
+      return { rejectionStack: stacks };
+    });
+  },
+
+  /**
    * Helper function for fetching the source location of a SavedFrame stack.
    *
    * @param SavedFrame stack
    *        The promise allocation stack frame
    * @return object
    *         Returns an object containing the source location of the SavedFrame
    *         stack.
    */
@@ -651,17 +681,18 @@ ObjectActor.prototype.requestTypes = {
   "property": ObjectActor.prototype.onProperty,
   "displayString": ObjectActor.prototype.onDisplayString,
   "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
   "decompile": ObjectActor.prototype.onDecompile,
   "release": ObjectActor.prototype.onRelease,
   "scope": ObjectActor.prototype.onScope,
   "dependentPromises": ObjectActor.prototype.onDependentPromises,
   "allocationStack": ObjectActor.prototype.onAllocationStack,
-  "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack
+  "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
+  "rejectionStack": ObjectActor.prototype.onRejectionStack
 };
 
 /**
  * Creates an actor to iterate over an object's property names and values.
  *
  * @param objectActor ObjectActor
  *        The object actor.
  * @param options Object