Bug 911216 - Part 6: Shim new promise-related Debugger.Object accessors using PromiseDebugging. r=shu
authorTill Schneidereit <till@tillschneidereit.net>
Tue, 22 Mar 2016 15:54:44 +0100
changeset 327793 17385ac2980201df48efb904afa7da8af547b251
parent 327792 672da685c6fc330d9b8d68e3f1db6cb951f34f8d
child 327794 021f70a04fadc6155030df3d30d8c4f01278dd6a
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu
bugs911216
milestone48.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 911216 - Part 6: Shim new promise-related Debugger.Object accessors using PromiseDebugging. r=shu
devtools/server/actors/object.js
js/ductwork/debugger/jsdebugger.jsm
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -6,17 +6,16 @@
 
 "use strict";
 
 const { Cu, Ci } = require("chrome");
 const { GeneratedLocation } = require("devtools/server/actors/common");
 const { DebuggerServer } = require("devtools/server/main")
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, dumpn } = DevToolsUtils;
-const PromiseDebugging = require("PromiseDebugging");
 
 loader.lazyRequireGetter(this, "ThreadSafeChromeUtils");
 
 const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
       "Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
       "Float64Array"];
 
 // Number of items to preview in objects, arrays, maps, sets, lists,
@@ -135,32 +134,29 @@ ObjectActor.prototype = {
   },
 
   /**
    * Returns an object exposing the internal Promise state.
    */
   _createPromiseState: function() {
     const { state, value, reason } = getPromiseState(this.obj);
     let promiseState = { state };
-    let rawPromise = this.obj.unsafeDereference();
 
     if (state == "fulfilled") {
       promiseState.value = this.hooks.createValueGrip(value);
     } else if (state == "rejected") {
       promiseState.reason = this.hooks.createValueGrip(reason);
     }
 
-    promiseState.creationTimestamp = Date.now() -
-      PromiseDebugging.getPromiseLifetime(rawPromise);
+    promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
 
-    // If the promise is not settled, avoid adding the timeToSettle property
-    // and catch the error thrown by PromiseDebugging.getTimeToSettle.
-    try {
-      promiseState.timeToSettle = PromiseDebugging.getTimeToSettle(rawPromise);
-    } catch(e) {}
+    // Only add the timeToSettle property if the Promise isn't pending.
+    if (state !== "pending") {
+      promiseState.timeToSettle = this.obj.promiseTimeToResolution;
+    }
 
     return promiseState;
   },
 
   /**
    * Releases this actor from the pool.
    */
   release: function() {
@@ -536,35 +532,32 @@ ObjectActor.prototype = {
    */
   onDependentPromises: function() {
     if (this.obj.class != "Promise") {
       return { error: "objectNotPromise",
                message: "'dependentPromises' request is only valid for " +
                         "object grips with a 'Promise' class." };
     }
 
-    let rawPromise = this.obj.unsafeDereference();
-    let promises = PromiseDebugging.getDependentPromises(rawPromise).map(p =>
-      this.hooks.createValueGrip(this.obj.makeDebuggeeValue(p)));
+    let promises = this.obj.promiseDependentPromises.map(p => this.hooks.createValueGrip(p));
 
     return { promises };
   },
 
   /**
    * Handle a protocol request to get the allocation stack of a promise.
    */
   onAllocationStack: function() {
     if (this.obj.class != "Promise") {
       return { error: "objectNotPromise",
                message: "'allocationStack' request is only valid for " +
                         "object grips with a 'Promise' class." };
     }
 
-    let rawPromise = this.obj.unsafeDereference();
-    let stack = PromiseDebugging.getAllocationStack(rawPromise);
+    let stack = this.obj.promiseAllocationSite;
     let allocationStacks = [];
 
     while (stack) {
       if (stack.source) {
         let source = this._getSourceOriginalLocation(stack);
 
         if (source) {
           allocationStacks.push(source);
@@ -583,18 +576,17 @@ ObjectActor.prototype = {
    */
   onFulfillmentStack: function() {
     if (this.obj.class != "Promise") {
       return { error: "objectNotPromise",
                message: "'fulfillmentStack' request is only valid for " +
                         "object grips with a 'Promise' class." };
     }
 
-    let rawPromise = this.obj.unsafeDereference();
-    let stack = PromiseDebugging.getFullfillmentStack(rawPromise);
+    let stack = this.obj.promiseResolutionSite;
     let fulfillmentStacks = [];
 
     while (stack) {
       if (stack.source) {
         let source = this._getSourceOriginalLocation(stack);
 
         if (source) {
           fulfillmentStacks.push(source);
@@ -613,18 +605,17 @@ ObjectActor.prototype = {
    */
   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 stack = this.obj.promiseResolutionSite;
     let rejectionStacks = [];
 
     while (stack) {
       if (stack.source) {
         let source = this._getSourceOriginalLocation(stack);
 
         if (source) {
           rejectionStacks.push(source);
@@ -1645,40 +1636,31 @@ DebuggerServer.ObjectActorPreviewers.Obj
 
     return true;
   },
 
   GenericObject,
 ];
 
 /**
- * Call PromiseDebugging.getState on this Debugger.Object's referent and wrap
- * the resulting `value` or `reason` properties in a Debugger.Object instance.
- *
- * See dom/webidl/PromiseDebugging.webidl
+ * Get thisDebugger.Object referent's `promiseState`.
  *
  * @returns Object
  *          An object of one of the following forms:
  *          - { state: "pending" }
  *          - { state: "fulfilled", value }
  *          - { state: "rejected", reason }
  */
 function getPromiseState(obj) {
   if (obj.class != "Promise") {
     throw new Error(
       "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
       "refer to Promise objects.");
   }
-
-  const state = PromiseDebugging.getState(obj.unsafeDereference());
-  return {
-    state: state.state,
-    value: obj.makeDebuggeeValue(state.value),
-    reason: obj.makeDebuggeeValue(state.reason)
-  };
+  return obj.promiseState;
 };
 
 /**
  * Determine if a given value is non-primitive.
  *
  * @param Any value
  *        The value to test.
  * @return Boolean
--- a/js/ductwork/debugger/jsdebugger.jsm
+++ b/js/ductwork/debugger/jsdebugger.jsm
@@ -16,9 +16,70 @@ this.EXPORTED_SYMBOLS = [ "addDebuggerTo
  *
  * For documentation on the API, see:
  *   https://developer.mozilla.org/en-US/docs/Tools/Debugger-API
  */
 
 const init = Components.classes["@mozilla.org/jsdebugger;1"].createInstance(Components.interfaces.IJSDebugger);
 this.addDebuggerToGlobal = function addDebuggerToGlobal(global) {
   init.addClass(global);
+  initPromiseDebugging(global);
 };
+
+function initPromiseDebugging(global) {
+  if (global.Debugger.Object.prototype.PromiseDebugging) {
+    return;
+  }
+
+  // If the PromiseDebugging object doesn't have all legacy functions, we're
+  // using the new accessors on Debugger.Object already.
+  if (!PromiseDebugging.getDependentPromises) {
+    return;
+  }
+
+  // Otherwise, polyfill them using PromiseDebugging.
+  global.Debugger.Object.prototype.PromiseDebugging = PromiseDebugging;
+  global.eval(polyfillSource);
+}
+
+let polyfillSource = `
+  Object.defineProperty(Debugger.Object.prototype, "promiseState", {
+    get() {
+      const state = this.PromiseDebugging.getState(this.unsafeDereference());
+      return {
+        state: state.state,
+        value: this.makeDebuggeeValue(state.value),
+        reason: this.makeDebuggeeValue(state.reason)
+      };
+    }
+  });
+  Object.defineProperty(Debugger.Object.prototype, "promiseLifetime", {
+    get() {
+      return this.PromiseDebugging.getPromiseLifetime(this.unsafeDereference());
+    }
+  });
+  Object.defineProperty(Debugger.Object.prototype, "promiseTimeToResolution", {
+    get() {
+      return this.PromiseDebugging.getTimeToSettle(this.unsafeDereference());
+    }
+  });
+  Object.defineProperty(Debugger.Object.prototype, "promiseDependentPromises", {
+    get() {
+      let promises = this.PromiseDebugging.getDependentPromises(this.unsafeDereference());
+      return promises.map(p => this.makeDebuggeeValue(p));
+    }
+  });
+  Object.defineProperty(Debugger.Object.prototype, "promiseAllocationSite", {
+    get() {
+      return this.PromiseDebugging.getAllocationStack(this.unsafeDereference());
+    }
+  });
+  Object.defineProperty(Debugger.Object.prototype, "promiseResolutionSite", {
+    get() {
+      let state = this.promiseState.state;
+      if (state === "fulfilled") {
+        return this.PromiseDebugging.getFullfillmentStack(this.unsafeDereference());
+      } else {
+        return this.PromiseDebugging.getRejectionStack(this.unsafeDereference());
+      }
+    }
+  });
+`;