Bug 911216 - Part 30: Enable SpiderMonkey Promise implementation. r=bz,efaust,bholley,Paolo,tromey,shu
☠☠ backed out by fe6985c6e616 ☠ ☠
authorTill Schneidereit <till@tillschneidereit.net>
Sat, 16 Jul 2016 15:05:12 +0200
changeset 347316 a80fdfc128b0c29dc34dd3fc28868252111a5d52
parent 347315 469d01eebea4e2055553289ce6542fc093460bbd
child 347317 a97ebab940c6ba0f19348c38d17a2193ee75c9ab
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, efaust, bholley, Paolo, tromey, shu
bugs911216
milestone50.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 30: Enable SpiderMonkey Promise implementation. r=bz,efaust,bholley,Paolo,tromey,shu Also contains folded version of the following patches that have to land at the same time with enabling the new implementation (or be backed out at the same time, if it comes to that): Add Promise checks to test_xrayToJS.xul. r=bholley Change Promise debugger hook tests to use Promise ctor instead of makeFakePromise. r=shu Change DOM interface tests to assume Promise is an ES builtin, not a DOM one. r=bz Remove some PromiseDebugging references. r=bz Adapt promise rejections test to new xray-unwrapping error. r=bz Fix expectations in browser_timelineMarkers tests. r=tromey
devtools/server/actors/script.js
devtools/shared/worker/loader.js
docshell/test/browser/browser_timelineMarkers-frame-04.js
docshell/test/browser/browser_timelineMarkers-frame-05.js
dom/bindings/test/test_promise_rejections_from_jsimplemented.html
dom/tests/mochitest/general/test_interfaces.html
dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
dom/workers/test/test_worker_interfaces.js
js/moz.configure
js/src/jit-test/lib/debuggerNXHelper.js
js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js
js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js
js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js
js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js
js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js
js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js
js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js
js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js
js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js
js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js
js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js
js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js
js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js
js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js
js/src/jit-test/tests/debug/bug-1102549.js
js/src/jsapi-tests/testPromise.cpp
js/xpconnect/tests/chrome/test_xrayToJS.xul
toolkit/components/extensions/ExtensionUtils.jsm
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -14,17 +14,16 @@ const { EnvironmentActor } = require("de
 const { FrameActor } = require("devtools/server/actors/frame");
 const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object");
 const { SourceActor, getSourceURL } = require("devtools/server/actors/source");
 const { DebuggerServer } = require("devtools/server/main");
 const { ActorClassWithSpec } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, dumpn, update, fetch } = DevToolsUtils;
 const promise = require("promise");
-const PromiseDebugging = require("PromiseDebugging");
 const xpcInspector = require("xpcInspector");
 const ScriptStore = require("./utils/ScriptStore");
 const { DevToolsWorker } = require("devtools/shared/worker/worker");
 const object = require("sdk/util/object");
 const { threadSpec } = require("devtools/shared/specs/script");
 
 const { defer, resolve, reject, all } = promise;
 
--- a/devtools/shared/worker/loader.js
+++ b/devtools/shared/worker/loader.js
@@ -312,22 +312,16 @@ function WorkerDebuggerLoader(options) {
 }
 
 this.WorkerDebuggerLoader = WorkerDebuggerLoader;
 
 // The following APIs rely on the use of Components, and the worker debugger
 // does not provide alternative definitions for them. Consequently, they are
 // stubbed out both on the main thread and worker threads.
 
-var PromiseDebugging = {
-  getState: function () {
-    throw new Error("PromiseDebugging is not available in workers!");
-  }
-};
-
 var chrome = {
   CC: undefined,
   Cc: undefined,
   ChromeWorker: undefined,
   Cm: undefined,
   Ci: undefined,
   Cu: undefined,
   Cr: undefined,
@@ -491,17 +485,16 @@ this.worker = new WorkerDebuggerLoader({
     "reportError": reportError,
     "rpc": rpc,
     "setImmediate": setImmediate,
     "URL": URL,
   },
   loadSubScript: loadSubScript,
   modules: {
     "Debugger": Debugger,
-    "PromiseDebugging": PromiseDebugging,
     "Services": Object.create(null),
     "chrome": chrome,
     "xpcInspector": xpcInspector
   },
   paths: {
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "": "resource://gre/modules/commonjs/",
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
--- a/docshell/test/browser/browser_timelineMarkers-frame-04.js
+++ b/docshell/test/browser/browser_timelineMarkers-frame-04.js
@@ -45,17 +45,21 @@ if (Services.prefs.getBoolPref("javascri
       markers = markers.filter(m => (m.name == "Javascript" &&
                                      m.causeName == "promise callback"));
       ok(markers.length > 0, "Found a Javascript marker");
 
       let frame = markers[0].stack;
       ok(frame.asyncParent !== null, "Parent frame has async parent");
       is(frame.asyncParent.asyncCause, "promise callback",
          "Async parent has correct cause");
-      is(frame.asyncParent.functionDisplayName, "do_promise",
+      let asyncFrame = frame.asyncParent;
+      // Skip over self-hosted parts of our Promise implementation.
+      while (asyncFrame.source === 'self-hosted')
+        asyncFrame = asyncFrame.parent;
+      is(asyncFrame.functionDisplayName, "do_promise",
          "Async parent has correct function name");
     }
   }, {
     desc: "Async stack trace on Javascript marker with script",
     searchFor: (markers) => {
       return markers.some(m => (m.name == "Javascript" &&
                                 m.causeName == "promise callback"));
     },
@@ -66,15 +70,19 @@ if (Services.prefs.getBoolPref("javascri
       markers = markers.filter(m => (m.name == "Javascript" &&
                                      m.causeName == "promise callback"));
       ok(markers.length > 0, "Found a Javascript marker");
 
       let frame = markers[0].stack;
       ok(frame.asyncParent !== null, "Parent frame has async parent");
       is(frame.asyncParent.asyncCause, "promise callback",
          "Async parent has correct cause");
-      is(frame.asyncParent.functionDisplayName, "do_promise_script",
+      let asyncFrame = frame.asyncParent;
+      // Skip over self-hosted parts of our Promise implementation.
+      while (asyncFrame.source === 'self-hosted')
+        asyncFrame = asyncFrame.parent;
+      is(asyncFrame.functionDisplayName, "do_promise_script",
          "Async parent has correct function name");
     }
   });
 }
 
 timelineContentTest(TESTS);
--- a/docshell/test/browser/browser_timelineMarkers-frame-05.js
+++ b/docshell/test/browser/browser_timelineMarkers-frame-05.js
@@ -86,20 +86,32 @@ if (Services.prefs.getBoolPref("javascri
     searchFor: "ConsoleTime",
     setup: function(docShell) {
       let resolver = makePromise();
       resolvePromise(resolver);
     },
     check: function(markers) {
       markers = markers.filter(m => m.name == "ConsoleTime");
       ok(markers.length > 0, "Promise marker includes stack");
-
+      ok(markers[0].stack.functionDisplayName == "testConsoleTime",
+         "testConsoleTime is on the stack");
       let frame = markers[0].endStack;
-      ok(frame.parent.asyncParent !== null, "Parent frame has async parent");
-      is(frame.parent.asyncParent.asyncCause, "promise callback",
+      ok(frame.functionDisplayName == "testConsoleTimeEnd",
+         "testConsoleTimeEnd is on the stack");
+
+      frame = frame.parent;
+      ok(frame.functionDisplayName == "makePromise/<",
+         "makePromise/< is on the stack");
+      let asyncFrame = frame.asyncParent;
+      ok(asyncFrame !== null, "Frame has async parent");
+      is(asyncFrame.asyncCause, "promise callback",
          "Async parent has correct cause");
-      is(frame.parent.asyncParent.functionDisplayName, "makePromise",
+      // Skip over self-hosted parts of our Promise implementation.
+      while (asyncFrame.source === 'self-hosted') {
+        asyncFrame = asyncFrame.parent;
+      }
+      is(asyncFrame.functionDisplayName, "makePromise",
          "Async parent has correct function name");
     }
   });
 }
 
 timelineContentTest(TESTS);
--- a/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
+++ b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
@@ -20,105 +20,114 @@ https://bugzilla.mozilla.org/show_bug.cg
     is(exn.name, name,
        "Should have the right exception name in test " + testNumber);
     is("filename" in exn ? exn.filename : exn.fileName, filename,
        "Should have the right file name in test " + testNumber);
     is(exn.message, message,
        "Should have the right message in test " + testNumber);
     is(exn.code, code, "Should have the right .code in test " + testNumber);
     if (message === "") {
-      is(exn.name, "NS_ERROR_UNEXPECTED",
+      is(exn.name, "InternalError",
          "Should have one of our synthetic exceptions in test " + testNumber);
     }
     is(exn.stack, stack, "Should have the right stack in test " + testNumber);
   }
 
   function ensurePromiseFail(testNumber, value) {
     ok(false, "Test " + testNumber + " should not have a fulfilled promise");
   }
 
   function doTest() {
     var t = new TestInterfaceJS();
     /* Async parent frames from pushPrefEnv don't show up in e10s.  */
     var isE10S = !SpecialPowers.isMainProcess();
     var asyncStack = SpecialPowers.getBoolPref("javascript.options.asyncstack");
     var ourFile = location.href;
-    var parentFrame = (asyncStack && !isE10S) ? `Async*@${ourFile}:121:3
+    var unwrapError = "Promise rejection value is a non-unwrappable cross-compartment wrapper.";
+    var parentFrame = (asyncStack && !isE10S) ? `Async*@${ourFile}:130:3
 ` : "";
 
     Promise.all([
       t.testPromiseWithThrowingChromePromiseInit().then(
           ensurePromiseFail.bind(null, 1),
-          checkExn.bind(null, 48, "NS_ERROR_UNEXPECTED", "", undefined,
-                        ourFile, 1,
-                        `doTest@${ourFile}:48:7
+          checkExn.bind(null, 49, "InternalError", unwrapError,
+                        undefined, ourFile, 1,
+                        `doTest@${ourFile}:49:7
 ` +
                         parentFrame)),
       t.testPromiseWithThrowingContentPromiseInit(function() {
           thereIsNoSuchContentFunction1();
         }).then(
           ensurePromiseFail.bind(null, 2),
-          checkExn.bind(null, 56, "ReferenceError",
+          checkExn.bind(null, 57, "ReferenceError",
                         "thereIsNoSuchContentFunction1 is not defined",
                         undefined, ourFile, 2,
-                        `doTest/<@${ourFile}:56:11
-doTest@${ourFile}:55:7
+                        `doTest/<@${ourFile}:57:11
+doTest@${ourFile}:56:7
 ` +
                         parentFrame)),
       t.testPromiseWithThrowingChromeThenFunction().then(
           ensurePromiseFail.bind(null, 3),
-          checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 3, "")),
+          checkExn.bind(null, 0, "InternalError", unwrapError, undefined, "", 3, asyncStack ? (`Async*doTest@${ourFile}:67:7
+` +
+                        parentFrame) : "")),
       t.testPromiseWithThrowingContentThenFunction(function() {
           thereIsNoSuchContentFunction2();
         }).then(
           ensurePromiseFail.bind(null, 4),
-          checkExn.bind(null, 70, "ReferenceError",
+          checkExn.bind(null, 73, "ReferenceError",
                         "thereIsNoSuchContentFunction2 is not defined",
                         undefined, ourFile, 4,
-                        `doTest/<@${ourFile}:70:11
+                        `doTest/<@${ourFile}:73:11
 ` +
-                        (asyncStack ? `Async*doTest@${ourFile}:69:7
+                        (asyncStack ? `Async*doTest@${ourFile}:72:7
 ` : "") +
                         parentFrame)),
       t.testPromiseWithThrowingChromeThenable().then(
           ensurePromiseFail.bind(null, 5),
-          checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 5, "")),
+          checkExn.bind(null, 0, "InternalError", unwrapError, undefined, "", 5, asyncStack ? (`Async*doTest@${ourFile}:84:7
+` +
+                        parentFrame) : "")),
       t.testPromiseWithThrowingContentThenable({
             then: function() { thereIsNoSuchContentFunction3(); }
         }).then(
           ensurePromiseFail.bind(null, 6),
-          checkExn.bind(null, 85, "ReferenceError",
+          checkExn.bind(null, 90, "ReferenceError",
                         "thereIsNoSuchContentFunction3 is not defined",
                         undefined, ourFile, 6,
-                        `doTest/<.then@${ourFile}:85:32
-`)),
+                        `doTest/<.then@${ourFile}:90:32
+` + (asyncStack ? `Async*doTest@${ourFile}:89:7\n` + parentFrame : ""))),
       t.testPromiseWithDOMExceptionThrowingPromiseInit().then(
           ensurePromiseFail.bind(null, 7),
-          checkExn.bind(null, 93, "NotFoundError",
+          checkExn.bind(null, 98, "NotFoundError",
                         "We are a second DOMException",
                         DOMException.NOT_FOUND_ERR, ourFile, 7,
-                        `doTest@${ourFile}:93:7
+                        `doTest@${ourFile}:98:7
 ` +
                         parentFrame)),
       t.testPromiseWithDOMExceptionThrowingThenFunction().then(
           ensurePromiseFail.bind(null, 8),
-          checkExn.bind(null, asyncStack ? 101 : 0, "NetworkError",
+          checkExn.bind(null, asyncStack ? 106 : 0, "NetworkError",
                          "We are a third DOMException",
                         DOMException.NETWORK_ERR, asyncStack ? ourFile : "", 8,
-                        (asyncStack ? `Async*doTest@${ourFile}:101:7
+                        (asyncStack ? `Async*doTest@${ourFile}:106:7
 ` +
                          parentFrame : ""))),
       t.testPromiseWithDOMExceptionThrowingThenable().then(
           ensurePromiseFail.bind(null, 9),
-          checkExn.bind(null, 0, "TypeMismatchError",
+          checkExn.bind(null, asyncStack ? 114 : 0, "TypeMismatchError",
                         "We are a fourth DOMException",
-                         DOMException.TYPE_MISMATCH_ERR, "", 9, "")),
+                        DOMException.TYPE_MISMATCH_ERR,
+                        asyncStack ? ourFile : "", 9,
+                        (asyncStack ? `Async*doTest@${ourFile}:114:7
+` +
+                         parentFrame : ""))),
     ]).then(SimpleTest.finish,
-            function() {
-              ok(false, "One of our catch statements totally failed");
+            function(err) {
+              ok(false, "One of our catch statements totally failed with err" + err + ', stack: ' + (err ? err.stack : ''));
               SimpleTest.finish();
             });
   }
 
   SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]},
                             doTest);
   </script>
 </head>
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -67,16 +67,17 @@ var ecmaGlobals =
     {name: "Intl", android: false},
     "Iterator",
     "JSON",
     "Map",
     "Math",
     {name: "NaN", xbl: false},
     "Number",
     "Object",
+    "Promise",
     "Proxy",
     "RangeError",
     "ReferenceError",
     "Reflect",
     "RegExp",
     "Set",
     {name: "SharedArrayBuffer", nightly: true},
     {name: "SIMD", nightly: true},
@@ -957,18 +958,16 @@ var interfaceNamesInGlobalScope =
     {name: "PresentationSession", disabled: true, permission: ["presentation"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PresentationSessionConnectEvent", disabled: true, permission: ["presentation"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ProcessingInstruction",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ProgressEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "Promise",
-// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PushManager", b2g: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PushSubscription", b2g: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PushSubscriptionOptions", b2g: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "RadioNodeList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -42,16 +42,17 @@ var ecmaGlobals =
     {name: "Intl", android: false},
     "Iterator",
     "JSON",
     "Map",
     "Math",
     "NaN",
     "Number",
     "Object",
+    "Promise",
     "Proxy",
     "RangeError",
     "ReferenceError",
     "Reflect",
     "RegExp",
     "Set",
     {name: "SharedArrayBuffer", nightly: true},
     {name: "SIMD", nightly: true},
@@ -170,18 +171,16 @@ var interfaceNamesInGlobalScope =
     "PerformanceMark",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMeasure",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PerformanceObserver", nightly: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PerformanceObserverEntryList", nightly: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "Promise",
-// IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushEvent", b2g: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushManager", b2g: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushMessageData", b2g: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushSubscription", b2g: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -42,16 +42,17 @@ var ecmaGlobals =
     {name: "Intl", android: false},
     "Iterator",
     "JSON",
     "Map",
     "Math",
     "NaN",
     "Number",
     "Object",
+    "Promise",
     "Proxy",
     "RangeError",
     "ReferenceError",
     "Reflect",
     "RegExp",
     "Set",
     {name: "SharedArrayBuffer", nightly: true},
     {name: "SIMD", nightly: true},
@@ -162,18 +163,16 @@ var interfaceNamesInGlobalScope =
     "PerformanceMark",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMeasure",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PerformanceObserver", nightly: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PerformanceObserverEntryList", nightly: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "Promise",
-// IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushManager", b2g: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushSubscription", b2g: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushSubscriptionOptions", b2g: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Request",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -34,17 +34,18 @@ def js_disable_shell(value):
     if not value:
         return True
 
 set_config('JS_DISABLE_SHELL', js_disable_shell)
 
 
 # Use SpiderMonkey Promise implementation if it's enabled
 # =======================================================
-js_option('--enable-sm-promise', help='Enable SpiderMonkey promises')
+js_option('--enable-sm-promise', default=True,
+          help='Enable SpiderMonkey promises')
 
 @depends('--enable-sm-promise')
 def sm_promise(value):
     if value:
         return True
 
 set_config('SPIDERMONKEY_PROMISE', sm_promise)
 set_define('SPIDERMONKEY_PROMISE', sm_promise)
--- a/js/src/jit-test/lib/debuggerNXHelper.js
+++ b/js/src/jit-test/lib/debuggerNXHelper.js
@@ -27,24 +27,26 @@ function testDebuggerHooksNX(dbg, g, tes
                    () => { g.eval("42"); });
 
   testDebuggerHook("onEnterFrame",
                    () => { g.eval("(() => {})()"); });
 
   testDebuggerHook("onNewGlobalObject",
                    () => { newGlobal(); });
 
-  testDebuggerHook("onNewPromise",
-                   () => { g.makeFakePromise(); });
+  if ('Promise' in g) {
+      testDebuggerHook("onNewPromise",
+                       () => { new g.Promise(()=>{}); });
 
-  testDebuggerHook("onPromiseSettled",
-                   () => {
-                     var p = g.makeFakePromise();
-                     g.settleFakePromise(p);
-                   });
+      testDebuggerHook("onPromiseSettled",
+                       () => {
+                         var p = new g.Promise(()=>{});
+                         g.settlePromiseNow(p);
+                       });
+  }
 
   // Hooks on frames.
   var onStepHit = false;
   var onPopHit = false;
   dbg.onEnterFrame = (frame) => {
     dbg.onEnterFrame = undefined;
 
     frame.onStep = () => {
--- a/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js
@@ -1,17 +1,19 @@
 // Test that the onNewPromise hook gets called when promises are allocated in
 // the scope of debuggee globals.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
 var dbg = new Debugger();
 var gw = dbg.addDebuggee(g);
 
 
 let promisesFound = [];
 dbg.onNewPromise = p => { promisesFound.push(p); };
 
-let p1 = g.makeFakePromise()
+let p1 = new g.Promise(function (){});
 dbg.enabled = false;
-let p2 = g.makeFakePromise();
+let p2 = new g.Promise(function (){});
 
 assertEq(promisesFound.indexOf(gw.makeDebuggeeValue(p1)) != -1, true);
 assertEq(promisesFound.indexOf(gw.makeDebuggeeValue(p2)) == -1, true);
--- a/js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js
@@ -1,24 +1,26 @@
 // onNewPromise handlers fire, until they are removed.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
 var dbg = new Debugger(g);
 var log;
 
 log = '';
-g.makeFakePromise();
+new g.Promise(function (){});
 assertEq(log, '');
 
 dbg.onNewPromise = function (promise) {
   log += 'n';
   assertEq(promise.seen, undefined);
   promise.seen = true;
 };
 
 log = '';
-g.makeFakePromise();
+new g.Promise(function (){});
 assertEq(log, 'n');
 
 log = '';
 dbg.onNewPromise = undefined;
-g.makeFakePromise();
+new g.Promise(function (){});
 assertEq(log, '');
--- a/js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js
@@ -1,9 +1,11 @@
 // onNewPromise handlers on different Debugger instances are independent.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
 var dbg1 = new Debugger(g);
 var log1;
 function h1(promise) {
   log1 += 'n';
   assertEq(promise.seen, undefined);
   promise.seen = true;
@@ -13,29 +15,29 @@ var dbg2 = new Debugger(g);
 var log2;
 function h2(promise) {
   log2 += 'n';
   assertEq(promise.seen, undefined);
   promise.seen = true;
 }
 
 log1 = log2 = '';
-g.makeFakePromise();
+new g.Promise(function (){});
 assertEq(log1, '');
 assertEq(log2, '');
 
 log1 = log2 = '';
 dbg1.onNewPromise = h1;
-g.makeFakePromise();
+new g.Promise(function (){});
 assertEq(log1, 'n');
 assertEq(log2, '');
 
 log1 = log2 = '';
 dbg2.onNewPromise = h2;
-g.makeFakePromise();
+new g.Promise(function (){});
 assertEq(log1, 'n');
 assertEq(log2, 'n');
 
 log1 = log2 = '';
 dbg1.onNewPromise = undefined;
-g.makeFakePromise();
+new g.Promise(function (){});
 assertEq(log1, '');
 assertEq(log2, 'n');
--- a/js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js
@@ -1,15 +1,17 @@
 // An onNewPromise handler can disable itself.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
 var dbg = new Debugger(g);
 var log;
 
 dbg.onNewPromise = function (promise) {
   log += 'n';
   dbg.onNewPromise = undefined;
 };
 
 log = '';
-g.makeFakePromise();
-g.makeFakePromise();
+new g.Promise(function (){});
+new g.Promise(function (){});
 assertEq(log, 'n');
--- a/js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js
@@ -1,24 +1,27 @@
 // Creating a promise within an onNewPromise handler causes a recursive handler
 // invocation.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
-var dbg = new Debugger(g);
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
 var log;
 var depth;
 
 dbg.onNewPromise = function (promise) {
   log += '('; depth++;
 
   assertEq(promise.seen, undefined);
   promise.seen = true;
 
   if (depth < 3)
-    g.makeFakePromise();
+      gw.executeInGlobal(`new Promise(_=>{})`);
 
   log += ')'; depth--;
 };
 
 log = '';
 depth = 0;
-g.makeFakePromise();
+new g.Promise(function (){});
 assertEq(log, '((()))');
--- a/js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js
@@ -1,35 +1,37 @@
 // Resumption values from onNewPromise handlers are disallowed.
+if (!('Promise' in this))
+    quit(0);
 
 load(libdir + 'asserts.js');
 
 var g = newGlobal();
 var dbg = new Debugger(g);
 var log;
 
 dbg.onNewPromise = function (g) { log += 'n'; return undefined; };
 log = '';
-assertEq(typeof g.makeFakePromise(), "object");
+assertEq(typeof new g.Promise(function (){}), "object");
 assertEq(log, 'n');
 
 dbg.uncaughtExceptionHook = function (ex) { assertEq(/disallowed/.test(ex), true); log += 'u'; }
 dbg.onNewPromise = function (g) { log += 'n'; return { return: "snoo" }; };
 log = '';
-assertEq(typeof g.makeFakePromise(), "object");
+assertEq(typeof new g.Promise(function (){}), "object");
 assertEq(log, 'nu');
 
 dbg.onNewPromise = function (g) { log += 'n'; return { throw: "snoo" }; };
 log = '';
-assertEq(typeof g.makeFakePromise(), "object");
+assertEq(typeof new g.Promise(function (){}), "object");
 assertEq(log, 'nu');
 
 dbg.onNewPromise = function (g) { log += 'n'; return null; };
 log = '';
-assertEq(typeof g.makeFakePromise(), "object");
+assertEq(typeof new g.Promise(function (){}), "object");
 assertEq(log, 'nu');
 
 dbg.uncaughtExceptionHook = function (ex) { assertEq(/foopy/.test(ex), true); log += 'u'; }
 dbg.onNewPromise = function (g) { log += 'n'; throw "foopy"; };
 log = '';
-assertEq(typeof g.makeFakePromise(), "object");
+assertEq(typeof new g.Promise(function (){}), "object");
 assertEq(log, 'nu');
 
--- a/js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js
@@ -1,13 +1,15 @@
 // Errors in onNewPromise handlers are reported correctly, and don't mess up the
 // promise creation.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
 var dbg = new Debugger(g);
 
 let e;
 dbg.uncaughtExceptionHook = ee => { e = ee; };
 dbg.onNewPromise = () => { throw new Error("woops!"); };
 
-assertEq(typeof g.makeFakePromise(), "object");
+assertEq(typeof new g.Promise(function (){}), "object");
 assertEq(!!e, true);
 assertEq(!!e.message.match(/woops/), true);
--- a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js
@@ -1,25 +1,27 @@
 // Test that the onPromiseSettled hook gets called when a promise settles.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
 var dbg = new Debugger();
 var gw = dbg.addDebuggee(g);
 
 let log = "";
 let pw;
 dbg.onPromiseSettled = pw_ => {
   pw = pw_;
   log += "s";
 };
 
-let p = g.makeFakePromise();
-g.settleFakePromise(p);
+let p = new g.Promise(function (){});
+g.settlePromiseNow(p);
 
 assertEq(log, "s");
 assertEq(pw, gw.makeDebuggeeValue(p));
 
 log = "";
 dbg.enabled = false;
-p = g.makeFakePromise();
-g.settleFakePromise(p);
+p = new g.Promise(function (){});
+g.settlePromiseNow(p);
 
 assertEq(log, "");
--- a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js
@@ -1,24 +1,26 @@
 // onPromiseSettled handlers fire, until they are removed.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
 var dbg = new Debugger(g);
 var log;
 
 log = '';
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log, '');
 
 dbg.onPromiseSettled = function (promise) {
   log += 's';
   assertEq(promise.seen, undefined);
   promise.seen = true;
 };
 
 log = '';
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log, 's');
 
 log = '';
 dbg.onPromiseSettled = undefined;
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log, '');
--- a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js
@@ -1,9 +1,11 @@
 // onPromiseSettled handlers on different Debugger instances are independent.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
 var dbg1 = new Debugger(g);
 var log1;
 function h1(promise) {
   log1 += 's';
   assertEq(promise.seen, undefined);
   promise.seen = true;
@@ -13,29 +15,29 @@ var dbg2 = new Debugger(g);
 var log2;
 function h2(promise) {
   log2 += 's';
   assertEq(promise.seen, undefined);
   promise.seen = true;
 }
 
 log1 = log2 = '';
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log1, '');
 assertEq(log2, '');
 
 log1 = log2 = '';
 dbg1.onPromiseSettled = h1;
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log1, 's');
 assertEq(log2, '');
 
 log1 = log2 = '';
 dbg2.onPromiseSettled = h2;
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log1, 's');
 assertEq(log2, 's');
 
 log1 = log2 = '';
 dbg1.onPromiseSettled = undefined;
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log1, '');
 assertEq(log2, 's');
--- a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js
@@ -1,15 +1,17 @@
 // An onPromiseSettled handler can disable itself.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
 var dbg = new Debugger(g);
 var log;
 
 dbg.onPromiseSettled = function (promise) {
   log += 's';
   dbg.onPromiseSettled = undefined;
 };
 
 log = '';
-g.settleFakePromise(g.makeFakePromise());
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log, 's');
--- a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js
@@ -1,24 +1,27 @@
 // Settling a promise within an onPromiseSettled handler causes a recursive
 // handler invocation.
+if (!('Promise' in this))
+    quit(0);
 
 var g = newGlobal();
-var dbg = new Debugger(g);
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
 var log;
 var depth;
 
 dbg.onPromiseSettled = function (promise) {
   log += '('; depth++;
 
   assertEq(promise.seen, undefined);
   promise.seen = true;
 
-  if (depth < 3)
-    g.settleFakePromise(g.makeFakePromise());
-
+  if (depth < 3) {
+    gw.executeInGlobal(`settlePromiseNow(new Promise(_=>{}));`);
+  }
   log += ')'; depth--;
 };
 
 log = '';
 depth = 0;
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(_=>{}));
 assertEq(log, '((()))');
--- a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js
@@ -1,35 +1,37 @@
 // Resumption values from onPromiseSettled handlers are disallowed.
+if (!('Promise' in this))
+    quit(0);
 
 load(libdir + 'asserts.js');
 
 var g = newGlobal();
 var dbg = new Debugger(g);
 var log;
 
 dbg.onPromiseSettled = function (g) { log += 's'; return undefined; };
 log = '';
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log, 's');
 
 dbg.uncaughtExceptionHook = function (ex) { assertEq(/disallowed/.test(ex), true); log += 'u'; }
 dbg.onPromiseSettled = function (g) { log += 's'; return { return: "snoo" }; };
 log = '';
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log, 'su');
 
 dbg.onPromiseSettled = function (g) { log += 's'; return { throw: "snoo" }; };
 log = '';
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log, 'su');
 
 dbg.onPromiseSettled = function (g) { log += 's'; return null; };
 log = '';
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log, 'su');
 
 dbg.uncaughtExceptionHook = function (ex) { assertEq(/foopy/.test(ex), true); log += 'u'; }
 dbg.onPromiseSettled = function (g) { log += 's'; throw "foopy"; };
 log = '';
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
 assertEq(log, 'su');
 
--- a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js
@@ -1,23 +1,25 @@
 // Test drainAllocationsLog() and [[Class]] names.
+if (!('Promise' in this))
+    quit(0);
 
 const root = newGlobal();
 const dbg = new Debugger();
 const wrappedRoot = dbg.addDebuggee(root)
 
 root.eval(
   `
   this.tests = [
     { expected: "Object",    test: () => new Object    },
     { expected: "Array",     test: () => []            },
     { expected: "Date",      test: () => new Date      },
     { expected: "RegExp",    test: () => /problems/    },
     { expected: "Int8Array", test: () => new Int8Array },
-    { expected: "Promise",   test: makeFakePromise     },
+    { expected: "Promise",   test: () => new Promise(function (){})},
   ];
   `
 );
 
 
 for (let { expected, test } of root.tests) {
   print(expected);
 
--- a/js/src/jit-test/tests/debug/bug-1102549.js
+++ b/js/src/jit-test/tests/debug/bug-1102549.js
@@ -1,5 +1,9 @@
 // |jit-test| error: log is not defined
+
+if (!this.Promise)
+    throw new Error('log is not defined');
+
 var g = newGlobal();
 var dbg = new Debugger(g);
 dbg.onPromiseSettled = function (g) { log += 's'; throw "foopy"; };
-g.settleFakePromise(g.makeFakePromise());
+g.settlePromiseNow(new g.Promise(function (){}));
--- a/js/src/jsapi-tests/testPromise.cpp
+++ b/js/src/jsapi-tests/testPromise.cpp
@@ -11,17 +11,19 @@
 
 using namespace JS;
 
 static bool executor_called = false;
 
 static bool
 PromiseExecutor(JSContext* cx, unsigned argc, Value* vp)
 {
+#ifdef DEBUG
     CallArgs args = CallArgsFromVp(argc, vp);
+#endif // DEBUG
     MOZ_ASSERT(args.length() == 2);
     MOZ_ASSERT(args[0].toObject().is<JSFunction>());
     MOZ_ASSERT(args[1].toObject().is<JSFunction>());
 
     executor_called = true;
     return true;
 }
 
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -35,35 +35,47 @@ https://bugzilla.mozilla.org/show_bug.cg
     }
   }
 
   typedArrayClasses = ['Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array',
                        'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array',
                        'Uint8ClampedArray'];
   errorObjectClasses = ['Error', 'InternalError', 'EvalError', 'RangeError', 'ReferenceError',
                         'SyntaxError', 'TypeError', 'URIError'];
+
+  // A simpleConstructors entry can either be the name of a constructor as a
+  // string, or an object containing the properties `name`, and `args`.
+  // In the former case, the constructor is invoked without any args; in the
+  // latter case, it is invoked with `args` as the arguments list.
   simpleConstructors = ['Object', 'Function', 'Array', 'Boolean', 'Date', 'Number',
-                        'String', 'RegExp', 'ArrayBuffer', 'WeakMap', 'Map', 'Set'].concat(typedArrayClasses)
-                                                                                   .concat(errorObjectClasses);
+                        'String', 'RegExp', 'ArrayBuffer', 'WeakMap', 'Map', 'Set',
+                        {name: 'Promise', args: [function(){}]}].concat(typedArrayClasses)
+                                                                .concat(errorObjectClasses);
 
   function go() {
     window.iwin = document.getElementById('ifr').contentWindow;
 
-    // Test constructors that can be instantiated with zero arguments.
+    // Test constructors that can be instantiated with zero arguments, or with
+    // a fixed set of arguments provided using `...rest`.
     for (var c of simpleConstructors) {
+      var args = [];
+      if (typeof c === 'object') {
+        args = c.args;
+        c = c.name;
+      }
       ok(iwin[c], "Constructors appear: " + c);
       is(iwin[c], Cu.unwaiveXrays(iwin.wrappedJSObject[c]),
          "we end up with the appropriate constructor: " + c);
-      is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin[c]).constructor), iwin[c],
+      is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin[c](...args)).constructor), iwin[c],
          "constructor property is set up right: " + c);
-      let expectedProto = /Opaque/.test(new iwin[c]) ? iwin['Object'].prototype
+      let expectedProto = /Opaque/.test(new iwin[c](...args)) ? iwin['Object'].prototype
                                                      : iwin[c].prototype;
-      is(Object.getPrototypeOf(new iwin[c]), expectedProto,
+      is(Object.getPrototypeOf(new iwin[c](...args)), expectedProto,
          "prototype is correct: " + c);
-      is(global(new iwin[c]), iwin, "Got the right global: " + c);
+      is(global(new iwin[c](...args)), iwin, "Got the right global: " + c);
     }
 
     // Test Object in more detail.
     var num = new iwin.Object(4);
     is(Cu.waiveXrays(num).valueOf(), 4, "primitive object construction works");
     is(global(num), iwin, "correct global for num");
     var obj = new iwin.Object();
     obj.foo = 2;
@@ -127,16 +139,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     testArray();
 
     testTypedArrays();
 
     testErrorObjects();
 
     testRegExp();
 
+    testPromise();
+
     // We could also test DataView and Iterator here for completeness, but it's
     // more trouble than it's worth.
 
     SimpleTest.finish();
   }
 
   // Maintain a static list of the properties that are available on each standard
   // prototype, so that we make sure to audit any new ones to make sure they're
@@ -223,16 +237,21 @@ https://bugzilla.mozilla.org/show_bug.cg
      "flags", "global", "ignoreCase", "multiline", "source", "sticky", "unicode",
      "lastIndex"];
   gConstructorProperties['RegExp'] =
     constructorProps(["input", "lastMatch", "lastParen",
                       "leftContext", "rightContext", "$1", "$2", "$3", "$4",
                       "$5", "$6", "$7", "$8", "$9", "$_", "$&", "$+",
                       "$`", "$'", Symbol.species])
 
+  gPrototypeProperties['Promise'] =
+    ["constructor", "catch", "then"];
+  gConstructorProperties['Promise'] =
+    constructorProps(["resolve", "reject", "all", "race", Symbol.species]);
+
   // Sort an array that may contain symbols as well as strings.
   function sortProperties(arr) {
     function sortKey(prop) {
       return typeof prop + ":" + prop.toString();
     }
     arr.sort((a, b) => sortKey(a) < sortKey(b) ? -1 : +1);
   }
 
@@ -841,13 +860,28 @@ for (var prop of props) {
 for (var prop of props) {
   Object.defineProperty(RegExp.prototype, prop, origDescs[prop]);
 }
 `);
       }
     }
   }
 
+  // Note: this is a small set of basic tests. More in-depth tests are located
+  // in test_promise_xrays.html.
+  function testPromise() {
+    testXray('Promise', new iwin.Promise(function(){}), new iwin.Promise(function(){}));
+
+    // Test catch and then.
+    var pr = new iwin.Promise(function(){});
+    isnot(pr.catch, Cu.unwaiveXrays(pr.wrappedJSObject.catch), "Different function identities");
+    is(Cu.getGlobalForObject(pr.catch), window, "Xray global is correct");
+    is(Cu.getGlobalForObject(pr.wrappedJSObject.catch), iwin, "Underlying global is correct");
+
+    isnot(pr.then, Cu.unwaiveXrays(pr.wrappedJSObject.then), "Different function identities");
+    is(Cu.getGlobalForObject(pr.then), window, "Xray global is correct");
+    is(Cu.getGlobalForObject(pr.wrappedJSObject.then), iwin, "Underlying global is correct");
+  }
 
   ]]>
   </script>
   <iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" />
 </window>
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -329,20 +329,18 @@ class BaseContext {
    *     `callback` function or resolving the wrapped promise.
    *
    * @param {function} [callback] The callback function to wrap
    *
    * @returns {Promise|undefined} If callback is null, a promise object
    *     belonging to the target scope. Otherwise, undefined.
    */
   wrapPromise(promise, callback = null) {
-    // Note: `promise instanceof this.cloneScope.Promise` returns true
-    // here even for promises that do not belong to the content scope.
     let runSafe = this.runSafe.bind(this);
-    if (promise.constructor === this.cloneScope.Promise) {
+    if (promise instanceof this.cloneScope.Promise) {
       runSafe = this.runSafeWithoutClone.bind(this);
     }
 
     if (callback) {
       promise.then(
         args => {
           if (this.unloaded) {
             dump(`Promise resolved after context unloaded\n`);