Bug 911216 - Part 7: Implement ES6 Promises in the JavaScript engine. r=efaust
authorTill Schneidereit <till@tillschneidereit.net>
Tue, 10 Nov 2015 12:34:00 +0100
changeset 289859 021f70a04fadc6155030df3d30d8c4f01278dd6a
parent 289858 17385ac2980201df48efb904afa7da8af547b251
child 289860 cf7722889ed96e7deaaaa9eef4b8b0caf8421d7d
push id74020
push usertschneidereit@gmail.com
push dateTue, 22 Mar 2016 23:02:59 +0000
treeherdermozilla-inbound@e947c9941fe1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust
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 7: Implement ES6 Promises in the JavaScript engine. r=efaust
js/public/Class.h
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
js/src/builtin/Promise.js
js/src/builtin/SelfHostingDefines.h
js/src/builtin/Utilities.js
js/src/js.msg
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testPromise.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsfriendapi.cpp
js/src/jsprototypes.h
js/src/moz.build
js/src/shell/js.cpp
js/src/tests/ecma_6/Class/extendBuiltinConstructors.js
js/src/tests/ecma_6/Promise/browser.js
js/src/tests/ecma_6/Promise/enqueue-promise-reactions.js
js/src/tests/ecma_6/Promise/get-wait-for-all-promise.js
js/src/tests/ecma_6/Promise/promise-all.js
js/src/tests/ecma_6/Promise/promise-basics.js
js/src/tests/ecma_6/Promise/promise-species.js
js/src/tests/ecma_6/Promise/promise-subclassing.js
js/src/tests/ecma_6/Promise/shell.js
js/src/vm/CommonPropertyNames.h
js/src/vm/GlobalObject.cpp
js/src/vm/GlobalObject.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/SelfHosting.cpp
js/src/vm/SelfHosting.h
js/xpconnect/wrappers/XrayWrapper.cpp
xpcom/base/CycleCollectedJSRuntime.cpp
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -852,17 +852,17 @@ Valueify(const JSClass* c)
 
 /**
  * Enumeration describing possible values of the [[Class]] internal property
  * value of objects.
  */
 enum ESClassValue {
     ESClass_Object, ESClass_Array, ESClass_Number, ESClass_String,
     ESClass_Boolean, ESClass_RegExp, ESClass_ArrayBuffer, ESClass_SharedArrayBuffer,
-    ESClass_Date, ESClass_Set, ESClass_Map,
+    ESClass_Date, ESClass_Set, ESClass_Map, ESClass_Promise,
 
     /** None of the above. */
     ESClass_Other
 };
 
 /* Fills |vp| with the unboxed value for boxed types, or undefined otherwise. */
 inline bool
 Unbox(JSContext* cx, JS::HandleObject obj, JS::MutableHandleValue vp);
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/Promise.cpp
@@ -0,0 +1,352 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "builtin/Promise.h"
+
+#include "jscntxt.h"
+
+#include "gc/Heap.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+static const JSFunctionSpec promise_methods[] = {
+    JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0),
+    JS_SELF_HOSTED_FN("then", "Promise_then", 2, 0),
+    JS_FS_END
+};
+
+static const JSFunctionSpec promise_static_methods[] = {
+    JS_SELF_HOSTED_FN("all", "Promise_static_all", 1, 0),
+    JS_SELF_HOSTED_FN("race", "Promise_static_race", 1, 0),
+    JS_SELF_HOSTED_FN("reject", "Promise_static_reject", 1, 0),
+    JS_SELF_HOSTED_FN("resolve", "Promise_static_resolve", 1, 0),
+    JS_FS_END
+};
+
+static const JSPropertySpec promise_static_properties[] = {
+    JS_SELF_HOSTED_SYM_GET(species, "Promise_static_get_species", 0),
+    JS_PS_END
+};
+
+// ES6, 25.4.3.1. steps 3-11.
+PromiseObject*
+PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */)
+{
+    MOZ_ASSERT(IsCallable(executor));
+
+    RootedObject usedProto(cx, proto);
+    bool wrappedProto = false;
+    // If the proto is wrapped, that means the current function is running
+    // with a different compartment active from the one the Promise instance
+    // is to be created in.
+    // See the comment in PromiseConstructor for details.
+    if (proto && IsWrapper(proto)) {
+        wrappedProto = true;
+        usedProto = CheckedUnwrap(proto);
+        if (!usedProto)
+            return nullptr;
+    }
+
+
+    // Step 3.
+    Rooted<PromiseObject*> promise(cx);
+    {
+        // Enter the unwrapped proto's compartment, if that's different from
+        // the current one.
+        // All state stored in a Promise's fixed slots must be created in the
+        // same compartment, so we get all of that out of the way here.
+        // (Except for the resolution functions, which are created below.)
+        mozilla::Maybe<AutoCompartment> ac;
+        if (wrappedProto)
+            ac.emplace(cx, usedProto);
+
+        promise = &NewObjectWithClassProto(cx, &class_, usedProto)->as<PromiseObject>();
+
+        // Step 4.
+        if (!promise)
+            return nullptr;
+
+        // Step 5.
+        promise->setFixedSlot(PROMISE_STATE_SLOT, Int32Value(PROMISE_STATE_PENDING));
+
+        // Step 6.
+        RootedArrayObject reactions(cx, NewDenseEmptyArray(cx));
+        if (!reactions)
+            return nullptr;
+        promise->setFixedSlot(PROMISE_FULFILL_REACTIONS_SLOT, ObjectValue(*reactions));
+
+        // Step 7.
+        reactions = NewDenseEmptyArray(cx);
+        if (!reactions)
+            return nullptr;
+        promise->setFixedSlot(PROMISE_REJECT_REACTIONS_SLOT, ObjectValue(*reactions));
+    }
+
+    RootedValue promiseVal(cx, ObjectValue(*promise));
+    if (wrappedProto && !cx->compartment()->wrap(cx, &promiseVal))
+        return nullptr;
+
+    // Step 8.
+    // The resolving functions are created in the compartment active when the
+    // (maybe wrapped) Promise constructor was called. They contain checks and
+    // can unwrap the Promise if required.
+    RootedValue resolvingFunctionsVal(cx);
+    if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().CreateResolvingFunctions,
+                                         &resolvingFunctionsVal))
+    {
+        return nullptr;
+    }
+    InvokeArgs args(cx);
+    if (!args.init(1))
+        return nullptr;
+    args.setCallee(resolvingFunctionsVal);
+    args.setThis(UndefinedValue());
+    args[0].set(promiseVal);
+
+    if (!Invoke(cx, args))
+        return nullptr;
+
+    RootedArrayObject resolvingFunctions(cx, &args.rval().toObject().as<ArrayObject>());
+    RootedValue resolveVal(cx, resolvingFunctions->getDenseElement(0));
+    MOZ_ASSERT(IsCallable(resolveVal));
+    RootedValue rejectVal(cx, resolvingFunctions->getDenseElement(1));
+    MOZ_ASSERT(IsCallable(rejectVal));
+
+    // Need to wrap the resolution functions before storing them on the Promise.
+    if (wrappedProto) {
+        AutoCompartment ac(cx, promise);
+        RootedValue wrappedResolveVal(cx, resolveVal);
+        RootedValue wrappedRejectVal(cx, rejectVal);
+        if (!cx->compartment()->wrap(cx, &wrappedResolveVal) ||
+            !cx->compartment()->wrap(cx, &wrappedRejectVal))
+        {
+            return nullptr;
+        }
+        promise->setFixedSlot(PROMISE_RESOLVE_FUNCTION_SLOT, wrappedResolveVal);
+        promise->setFixedSlot(PROMISE_REJECT_FUNCTION_SLOT, wrappedRejectVal);
+    } else {
+        promise->setFixedSlot(PROMISE_RESOLVE_FUNCTION_SLOT, resolveVal);
+        promise->setFixedSlot(PROMISE_REJECT_FUNCTION_SLOT, rejectVal);
+    }
+
+    // Step 9.
+    InvokeArgs args2(cx);
+    if (!args2.init(2))
+        return nullptr;
+    args2.setCallee(ObjectValue(*executor));
+    args2.setThis(UndefinedValue());
+    args2[0].set(resolveVal);
+    args2[1].set(rejectVal);
+    bool success = Invoke(cx, args2);
+
+    // Step 10.
+    if (!success) {
+        RootedValue exceptionVal(cx);
+        // Not much we can do about uncatchable exceptions, so just bail
+        // for those.
+        if (!cx->isExceptionPending() || !GetAndClearException(cx, &exceptionVal))
+            return nullptr;
+        InvokeArgs args(cx);
+        if (!args.init(1))
+            return nullptr;
+        args.setCallee(rejectVal);
+        args.setThis(UndefinedValue());
+        args[0].set(exceptionVal);
+
+        if (!Invoke(cx, args))
+            return nullptr;
+    }
+
+    // Step 11.
+    return promise;
+}
+
+namespace js {
+
+// ES6, 25.4.3.1.
+bool
+PromiseConstructor(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Step 1.
+    if (!ThrowIfNotConstructing(cx, args, "Promise"))
+        return false;
+
+    // Step 2.
+    RootedValue executorVal(cx, args.get(0));
+    if (!IsCallable(executorVal))
+        return ReportIsNotFunction(cx, executorVal);
+    RootedObject executor(cx, &executorVal.toObject());
+
+    // Steps 3-10.
+    RootedObject newTarget(cx, &args.newTarget().toObject());
+    RootedObject originalNewTarget(cx, newTarget);
+    bool needsWrapping = false;
+
+    // If the constructor is called via an Xray wrapper, then the newTarget
+    // hasn't been unwrapped. We want that because, while the actual instance
+    // should be created in the target compartment, the constructor's code
+    // should run in the wrapper's compartment.
+    //
+    // This is so that the resolve and reject callbacks get created in the
+    // wrapper's compartment, which is required for code in that compartment
+    // to freely interact with it, and, e.g., pass objects as arguments, which
+    // it wouldn't be able to if the callbacks were themselves wrapped in Xray
+    // wrappers.
+    //
+    // At the same time, just creating the Promise itself in the wrapper's
+    // compartment wouldn't be helpful: if the wrapper forbids interactions
+    // with objects except for specific actions, such as calling them, then
+    // the code we want to expose it to can't actually treat it as a Promise:
+    // calling .then on it would throw, for example.
+    //
+    // Another scenario where it's important to create the Promise in a
+    // different compartment from the resolution functions is when we want to
+    // give non-privileged code a Promise resolved with the result of a
+    // Promise from privileged code; as a return value of a JS-implemented
+    // API, say. If the resolution functions were unprivileged, then resolving
+    // with a privileged Promise would cause `resolve` to attempt accessing
+    // .then on the passed Promise, which would throw an exception, so we'd
+    // just end up with a rejected Promise. Really, we want to chain the two
+    // Promises, with the unprivileged one resolved with the resolution of the
+    // privileged one.
+    if (IsWrapper(newTarget)) {
+        newTarget = CheckedUnwrap(newTarget);
+        MOZ_ASSERT(newTarget);
+        MOZ_ASSERT(newTarget != originalNewTarget);
+        {
+            AutoCompartment ac(cx, newTarget);
+            RootedObject promiseCtor(cx);
+            if (!GetBuiltinConstructor(cx, JSProto_Promise, &promiseCtor))
+                return false;
+
+            // Promise subclasses don't get the special Xray treatment, so
+            // we only need to do the complex wrapping and unwrapping scheme
+            // described above for instances of Promise itself.
+            if (newTarget == promiseCtor)
+                needsWrapping = true;
+        }
+    }
+
+    RootedObject proto(cx);
+    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+        return false;
+    if (needsWrapping && !cx->compartment()->wrap(cx, &proto))
+        return false;
+    Rooted<PromiseObject*> promise(cx, PromiseObject::create(cx, executor, proto));
+    if (!promise)
+        return false;
+
+    // Step 11.
+    args.rval().setObject(*promise);
+    if (needsWrapping)
+        return cx->compartment()->wrap(cx, args.rval());
+    return true;
+}
+
+
+
+bool
+PromiseObject::resolve(JSContext* cx, HandleValue resolutionValue)
+{
+    if (this->getFixedSlot(PROMISE_STATE_SLOT).toInt32() != unsigned(JS::PromiseState::Pending))
+        return true;
+
+    RootedValue funVal(cx, this->getReservedSlot(PROMISE_RESOLVE_FUNCTION_SLOT));
+    MOZ_ASSERT(funVal.toObject().is<JSFunction>());
+
+    InvokeArgs args(cx);
+    if (!args.init(1))
+        return false;
+    args.setCallee(funVal);
+    args.setThis(UndefinedValue());
+    args[0].set(resolutionValue);
+    return Invoke(cx, args);
+}
+
+bool
+PromiseObject::reject(JSContext* cx, HandleValue rejectionValue)
+{
+    if (this->getFixedSlot(PROMISE_STATE_SLOT).toInt32() != unsigned(JS::PromiseState::Pending))
+        return true;
+
+    RootedValue funVal(cx, this->getReservedSlot(PROMISE_REJECT_FUNCTION_SLOT));
+    MOZ_ASSERT(funVal.toObject().is<JSFunction>());
+
+    InvokeArgs args(cx);
+    if (!args.init(1))
+        return false;
+    args.setCallee(funVal);
+    args.setThis(UndefinedValue());
+    args[0].set(rejectionValue);
+    return Invoke(cx, args);
+}
+
+} // namespace js
+
+static JSObject*
+CreatePromisePrototype(JSContext* cx, JSProtoKey key)
+{
+    return cx->global()->createBlankPrototype(cx, &PromiseObject::protoClass_);
+}
+
+const Class PromiseObject::class_ = {
+    "Promise",
+    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Promise) |
+    JSCLASS_HAS_XRAYED_CONSTRUCTOR,
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* getProperty */
+    nullptr, /* setProperty */
+    nullptr, /* enumerate */
+    nullptr, /* resolve */
+    nullptr, /* mayResolve */
+    nullptr, /* finalize */
+    nullptr, /* call */
+    nullptr, /* hasInstance */
+    nullptr, /* construct */
+    nullptr, /* trace */
+    {
+        GenericCreateConstructor<PromiseConstructor, 1, gc::AllocKind::FUNCTION>,
+        CreatePromisePrototype,
+        promise_static_methods,
+        promise_static_properties,
+        promise_methods
+    }
+};
+
+const Class PromiseObject::protoClass_ = {
+    "PromiseProto",
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Promise),
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* getProperty */
+    nullptr, /* setProperty */
+    nullptr, /* enumerate */
+    nullptr, /* resolve */
+    nullptr, /* mayResolve */
+    nullptr, /* finalize */
+    nullptr, /* call */
+    nullptr, /* hasInstance */
+    nullptr, /* construct */
+    nullptr, /* trace  */
+    {
+        DELEGATED_CLASSSPEC(&PromiseObject::class_.spec),
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        ClassSpec::IsDelegated
+    }
+};
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/Promise.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef builtin_Promise_h
+#define builtin_Promise_h
+
+#include "builtin/SelfHostingDefines.h"
+#include "vm/NativeObject.h"
+
+namespace js {
+
+class AutoSetNewObjectMetadata;
+
+class PromiseObject : public NativeObject
+{
+  public:
+    static const unsigned RESERVED_SLOTS = 6;
+    static const Class class_;
+    static const Class protoClass_;
+    static PromiseObject* create(JSContext* cx, HandleObject executor,
+                                 HandleObject proto = nullptr);
+
+    JS::PromiseState state() {
+        int32_t state = getReservedSlot(PROMISE_STATE_SLOT).toInt32();
+        MOZ_ASSERT(state >= 0 && state <= int32_t(JS::PromiseState::Rejected));
+        return JS::PromiseState(state);
+    }
+
+    bool resolve(JSContext* cx, HandleValue resolutionValue);
+    bool reject(JSContext* cx, HandleValue rejectionValue);
+};
+
+} // namespace js
+
+#endif /* builtin_Promise_h */
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/Promise.js
@@ -0,0 +1,815 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// ES6, 25.4.1.2.
+// This object is used to verify that an object is a PromiseReaction record.
+var PromiseReactionRecordProto = {__proto__: null};
+
+
+// ES6, 25.4.1.3.
+function CreateResolvingFunctions(promise) {
+    // The callbacks created here can deal with Promises wrapped in cross-
+    // compartment wrappers. That's required because in some circumstances,
+    // they're created in a higher-privileged compartment from the Promise,
+    // so they can be invoked seamlessly by code in that compartment.
+    //
+    // See the comment in PromiseConstructor (in builtin/Promise.cpp) for more
+    // details.
+    let unwrap = false;
+    if (!IsPromise(promise)) {
+        assert(IsWrappedPromise(promise),
+               "CreateResolvingFunctions expects arg0 to be a - maybe wrapped - promise");
+        unwrap = true;
+    }
+
+    // Step 1.
+    let alreadyResolved = false;
+
+    // Steps 2-4.
+    // ES6, 25.4.1.3.2. Inlined here so we can use an upvar instead of a slot to
+    // store promise and alreadyResolved, and share the latter with reject below.
+    function resolve(resolution) {
+        // Steps 1-3 (implicit).
+
+        // Step 4.
+        if (alreadyResolved)
+            return undefined;
+
+        // Step 5.
+        alreadyResolved = true;
+
+        // Step 6.
+        // We know |promise| is an object, so using strict equality instead of
+        // SameValue is fine.
+        if (resolution === promise) {
+            // Step 6.a.
+            let selfResolutionError = GetTypeError(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
+
+            // Step 6.b.
+            if (unwrap) {
+                return callFunction(CallPromiseMethodIfWrapped, promise, selfResolutionError,
+                                    "RejectUnwrappedPromise");
+            }
+            return RejectPromise(promise, selfResolutionError);
+        }
+
+        // Step 7.
+        if (!IsObject(resolution)) {
+            if (unwrap) {
+                return callFunction(CallPromiseMethodIfWrapped, promise, resolution,
+                                    "FulfillUnwrappedPromise");
+            }
+            return FulfillPromise(promise, resolution);
+        }
+
+        // Steps 8-9.
+        let then;
+        try {
+            then = resolution.then;
+        } catch (e) {
+            if (unwrap) {
+                return callFunction(CallPromiseMethodIfWrapped, promise, e,
+                                    "RejectUnwrappedPromise");
+            }
+            return RejectPromise(promise, e);
+        }
+
+        // Step 10 (implicit).
+
+        // Step 11.
+        if (!IsCallable(then)) {
+            if (unwrap) {
+                return callFunction(CallPromiseMethodIfWrapped, promise, resolution,
+                                    "FulfillUnwrappedPromise");
+            }
+            return FulfillPromise(promise, resolution);
+        }
+
+        // Step 12.
+        EnqueuePromiseResolveThenableJob(promise, resolution, then);
+
+        // Step 13.
+        return undefined;
+    }
+
+    // Steps 5-7.
+    // ES6, 25.4.1.3.2.
+    function reject(reason) {
+        // Steps 1-3 (implicit).
+
+        // Step 4.
+        if (alreadyResolved)
+            return undefined;
+
+        // Step 5.
+        alreadyResolved = true;
+
+        // Step 6.
+        if (unwrap) {
+            return callFunction(CallPromiseMethodIfWrapped, promise, reason,
+                                "RejectUnwrappedPromise");
+        }
+        return RejectPromise(promise, reason);
+    }
+
+    // Return an array instead of an object with resolve/reject properties
+    // to make value extraction from C++ easier.
+    return [resolve, reject];
+}
+
+// ES6, 25.4.1.4.
+function FulfillPromise(promise, value) {
+    return ResolvePromise(promise, value, PROMISE_FULFILL_REACTIONS_SLOT, PROMISE_STATE_FULFILLED);
+}
+function FulfillUnwrappedPromise(value) {
+    return ResolvePromise(this, value, PROMISE_FULFILL_REACTIONS_SLOT, PROMISE_STATE_FULFILLED);
+}
+
+// Commoned-out implementation of 25.4.1.4. and 25.4.1.7.
+function ResolvePromise(promise, valueOrReason, reactionsSlot, state) {
+    // Step 1.
+    assert(GetPromiseState(promise) === PROMISE_STATE_PENDING,
+           "Can't resolve non-pending promise");
+
+    // Step 2.
+    var reactions = UnsafeGetObjectFromReservedSlot(promise, reactionsSlot);
+
+    // Step 3.
+    UnsafeSetReservedSlot(promise, PROMISE_RESULT_SLOT, valueOrReason);
+
+    // Step 4.
+    UnsafeSetReservedSlot(promise, PROMISE_FULFILL_REACTIONS_SLOT, null);
+
+    // Step 5.
+    UnsafeSetReservedSlot(promise, PROMISE_REJECT_REACTIONS_SLOT, null);
+
+    // Step 6.
+    UnsafeSetReservedSlot(promise, PROMISE_STATE_SLOT, state);
+
+    // Also null out the resolve/reject functions so they can be GC'd.
+    UnsafeSetReservedSlot(promise, PROMISE_RESOLVE_FUNCTION_SLOT, null);
+    UnsafeSetReservedSlot(promise, PROMISE_REJECT_FUNCTION_SLOT, null);
+
+    // Step 7.
+    return TriggerPromiseReactions(reactions, valueOrReason);
+}
+
+// Used to verify that an object is a PromiseCapability record.
+var PromiseCapabilityRecordProto = {__proto__: null};
+
+// ES6, 25.4.1.5.
+// Creates PromiseCapability records, see 25.4.1.1.
+function NewPromiseCapability(C) {
+    // Steps 1-2.
+    if (!IsConstructor(C))
+        ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, 0);
+
+    // Step 3. Replaced by individual fields, combined in step 11.
+    let resolve;
+    let reject;
+
+    // Steps 4-5.
+    // ES6, 25.4.1.5.1. Inlined here so we can use an upvar instead of a slot to
+    // store promiseCapability.
+    function GetCapabilitiesExecutor(resolve_, reject_) {
+        // Steps 1-2 (implicit).
+
+        // Steps 3-4.
+        if (resolve !== undefined || reject !== undefined)
+            ThrowTypeError(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);
+        resolve = resolve_;
+        reject = reject_;
+    }
+
+    // Steps 6-7.
+    let promise = new C(GetCapabilitiesExecutor);
+
+    // Step 8.
+    if (!IsCallable(resolve))
+        ThrowTypeError(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
+
+    // Step 9.
+    if (!IsCallable(reject))
+        ThrowTypeError(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
+
+    // Steps 10-11.
+    return {
+        __proto__: PromiseCapabilityRecordProto,
+        promise,
+        resolve,
+        reject
+    };
+}
+
+// ES6, 25.4.1.6. is implemented as an intrinsic in SelfHosting.cpp.
+
+// ES6, 25.4.1.7.
+function RejectPromise(promise, reason) {
+    return ResolvePromise(promise, reason, PROMISE_REJECT_REACTIONS_SLOT, PROMISE_STATE_REJECTED);
+}
+
+function RejectUnwrappedPromise(reason) {
+    return ResolvePromise(this, reason, PROMISE_REJECT_REACTIONS_SLOT, PROMISE_STATE_REJECTED);
+}
+
+// ES6, 25.4.1.8.
+function TriggerPromiseReactions(reactions, argument) {
+    // Step 1.
+    for (var i = 0, len = reactions.length; i < len; i++)
+        EnqueuePromiseReactionJob(reactions[i], argument);
+    // Step 2 (implicit).
+}
+
+// ES6, 25.4.2.1.
+function EnqueuePromiseReactionJob(reaction, argument) {
+    _EnqueuePromiseJob(function PromiseReactionJob() {
+        // Step 1.
+        assert(IsPromiseReaction(reaction), "Invalid promise reaction record");
+
+        // Step 2.
+        let promiseCapability = reaction.capabilities;
+
+        // Step 3.
+        let handler = reaction.handler;
+        let handlerResult = argument;
+        let shouldReject = false;
+
+        // Steps 4-6.
+        if (handler === PROMISE_HANDLER_IDENTITY) {
+            // handlerResult = argument; (implicit)
+        } else if (handler === PROMISE_HANDLER_THROWER) {
+            // handlerResult = argument; (implicit)
+            shouldReject = true;
+        } else {
+            try {
+                handlerResult = callContentFunction(handler, undefined, argument);
+            } catch (e) {
+                handlerResult = e;
+                shouldReject = true;
+            }
+        }
+
+        // Step 7.
+        if (shouldReject) {
+            // Step 7.a.
+            callContentFunction(promiseCapability.reject, undefined, handlerResult);
+
+            // Step 7.b.
+            return;
+        }
+
+        // Steps 8-9.
+        return callContentFunction(promiseCapability.resolve, undefined, handlerResult);
+    });
+}
+
+// ES6, 25.4.2.2.
+function EnqueuePromiseResolveThenableJob(promiseToResolve, thenable, then) {
+    _EnqueuePromiseJob(function PromiseResolveThenableJob() {
+        // Step 1.
+        let {0: resolve, 1: reject} = CreateResolvingFunctions(promiseToResolve);
+
+        // Steps 2-3.
+        try {
+            // Step 2.
+            callContentFunction(then, thenable, resolve, reject);
+        } catch (thenCallResult) {
+            // Steps 3.a-b.
+            callFunction(reject, undefined, thenCallResult);
+        }
+
+        // Step 4 (implicit, no need to return anything).
+    });
+}
+
+// ES6, 25.4.3.1. (Implemented in C++).
+
+// ES7 2016-01-21 draft, 25.4.4.1.
+function Promise_static_all(iterable) {
+    // Step 1.
+    let C = this;
+
+    // Step 2.
+    if (!IsObject(C))
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.all call");
+
+    // Step 3.
+    let promiseCapability = NewPromiseCapability(C);
+
+    // Steps 4-5.
+    let iterator;
+    try {
+        iterator = GetIterator(iterable);
+    } catch (e) {
+        callContentFunction(promiseCapability.reject, undefined, e);
+        return promiseCapability.promise;
+    }
+
+    // Step 6.
+    let iteratorRecord = {__proto__: null, iterator, done: false};
+
+    // Steps 7-9.
+    try {
+        // Steps 7,9.
+        return PerformPromiseAll(iteratorRecord, C, promiseCapability);
+    } catch (e) {
+        // Step 8.a.
+        // TODO: implement iterator closing.
+
+        // Step 8.b.
+        callContentFunction(promiseCapability.reject, undefined, e);
+        return promiseCapability.promise;
+    }
+}
+
+// ES6, 25.4.4.1.1.
+function PerformPromiseAll(iteratorRecord, constructor, resultCapability) {
+    // Step 1.
+    assert(IsConstructor(constructor), "PerformPromiseAll called with non-constructor");
+
+    // Step 2.
+    assert(IsPromiseCapability(resultCapability), "Invalid promise capability record");
+
+    // Step 3.
+    // Immediately create an Array instead of a List, so we can skip step 6.d.iii.1.
+    //
+    // We might be dealing with a wrapped instance from another Realm. In that
+    // case, we want to create the `values` array in that other Realm so if
+    // it's less-privileged than the current one, code in that Realm can still
+    // work with the array.
+    let values = IsPromise(resultCapability.promise) || !IsWrappedPromise(resultCapability.promise)
+                 ? []
+                 : NewArrayInCompartment(constructor);
+    let valuesCount = 0;
+
+    // Step 4.
+    let remainingElementsCount = {value: 1};
+
+    // Step 5.
+    let index = 0;
+
+    // Step 6.
+    let iterator = iteratorRecord.iterator;
+    let next;
+    let nextValue;
+    while (true) {
+        try {
+            // Step 6.a.
+            next = callContentFunction(iterator.next, iterator);
+            if (!IsObject(next))
+                ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE);
+        } catch (e) {
+            // Step 6.b.
+            iteratorRecord.done = true;
+
+            // Step 6.c.
+            throw (e);
+        }
+
+        // Step 6.d.
+        if (next.done) {
+            // Step 6.d.i.
+            iteratorRecord.done = true;
+
+            // Step 6.d.ii.
+            remainingElementsCount.value--;
+            assert(remainingElementsCount.value >= 0,
+                   "remainingElementsCount mustn't be negative.");
+
+            // Step 6.d.iii.
+            if (remainingElementsCount.value === 0)
+                callContentFunction(resultCapability.resolve, undefined, values);
+
+            // Step 6.d.iv.
+            return resultCapability.promise;
+        }
+        try {
+            // Step 6.e.
+            nextValue = next.value;
+        } catch (e) {
+            // Step 6.f.
+            iteratorRecord.done = true;
+
+            // Step 6.g.
+            throw e;
+        }
+
+        // Step 6.h.
+        _DefineDataProperty(values, valuesCount++, undefined);
+
+        // Steps 6.i-j.
+        let nextPromise = callContentFunction(constructor.resolve, constructor, nextValue);
+
+        // Steps 6.k-p.
+        let resolveElement = CreatePromiseAllResolveElementFunction(index, values,
+                                                                    resultCapability,
+                                                                    remainingElementsCount);
+
+        // Step 6.q.
+        remainingElementsCount.value++;
+
+        // Steps 6.r-s.
+        let result = callContentFunction(nextPromise.then, nextPromise, resolveElement,
+                                         resultCapability.reject);
+
+        // Step 6.t.
+        index++;
+    }
+}
+
+/**
+ * Unforgeable version of Promise.all for internal use.
+ *
+ * Takes a dense array of Promise objects and returns a promise that's
+ * resolved with an array of resolution values when all those promises ahve
+ * been resolved, or rejected with the rejection value of the first rejected
+ * promise.
+ *
+ * Asserts if the array isn't dense or one of the entries isn't a Promise.
+ */
+function GetWaitForAllPromise(promises) {
+    let resultCapability = NewPromiseCapability(GetBuiltinConstructor('Promise'));
+    let allPromise = resultCapability.promise;
+
+    // Step 3.
+    // Immediately create an Array instead of a List, so we can skip step 6.d.iii.1.
+    let values = [];
+    let valuesCount = 0;
+
+    // Step 4.
+    let remainingElementsCount = {value: 0};
+
+    // Step 6.
+    for (let i = 0; i < promises.length; i++) {
+        // Parts of step 6 for deriving next promise, vastly simplified.
+        assert(callFunction(std_Object_hasOwnProperty, promises, i),
+               "GetWaitForAllPromise must be called with a dense array of promises");
+        let nextPromise = promises[i];
+        assert(IsPromise(nextPromise) || IsWrappedPromise(nextPromise),
+               "promises list must only contain possibly wrapped promises");
+
+        // Step 6.h.
+        _DefineDataProperty(values, valuesCount++, undefined);
+
+        // Steps 6.k-p.
+        let resolveElement = CreatePromiseAllResolveElementFunction(i, values,
+                                                                    resultCapability,
+                                                                    remainingElementsCount);
+
+        // Step 6.q.
+        remainingElementsCount.value++;
+
+        // Steps 6.r-s, very roughly.
+        EnqueuePromiseReactions(nextPromise, allPromise, resolveElement, resultCapability.reject);
+    }
+
+    if (remainingElementsCount.value === 0)
+        callFunction(resultCapability.resolve, undefined, values);
+
+    return allPromise;
+}
+
+// ES6, 25.4.4.1.2.
+function CreatePromiseAllResolveElementFunction(index, values, promiseCapability,
+                                                remainingElementsCount)
+{
+    var alreadyCalled = false;
+    return function PromiseAllResolveElementFunction(x) {
+        // Steps 1-2.
+        if (alreadyCalled)
+            return undefined;
+
+        // Step 3.
+        alreadyCalled = true;
+
+        // Steps 4-7 (implicit).
+
+        // Step 8.
+        // Note: this can't throw because the slot was initialized to `undefined` earlier.
+        values[index] = x;
+
+        // Step 9.
+        remainingElementsCount.value--;
+        assert(remainingElementsCount.value >= 0, "remainingElementsCount mustn't be negative.");
+
+        // Step 10.
+        if (remainingElementsCount.value === 0) {
+            // Step 10.a (implicit).
+
+            // Step 10.b.
+            return callContentFunction(promiseCapability.resolve, undefined, values);
+        }
+
+        // Step 11 (implicit).
+    };
+}
+
+// ES7, 2016-01-21 draft, 25.4.4.3.
+function Promise_static_race(iterable) {
+    // Step 1.
+    let C = this;
+
+    // Step 2.
+    if (!IsObject(C))
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.race call");
+
+    // step 3.
+    let promiseCapability = NewPromiseCapability(C);
+
+    // Steps 4-5.
+    let iterator;
+    try {
+        iterator = GetIterator(iterable);
+    } catch (e) {
+        callContentFunction(promiseCapability.reject, undefined, e);
+        return promiseCapability.promise;
+    }
+
+    // Step 6.
+    let iteratorRecord = {__proto__: null, iterator, done: false};
+
+    // Steps 7-9.
+    try {
+        // Steps 7,9.
+        return PerformPromiseRace(iteratorRecord, promiseCapability, C);
+    } catch (e) {
+        // Step 8.a.
+        // TODO: implement iterator closing.
+
+        // Step 8.b.
+        callContentFunction(promiseCapability.reject, undefined, e);
+        return promiseCapability.promise;
+    }
+}
+
+// ES7, 2016-01-21 draft, 25.4.4.3.1.
+function PerformPromiseRace(iteratorRecord, resultCapability, C) {
+    assert(IsConstructor(C), "PerformPromiseRace called with non-constructor");
+    assert(IsPromiseCapability(resultCapability), "Invalid promise capability record");
+
+    // Step 1.
+    let iterator = iteratorRecord.iterator;
+    let next;
+    let nextValue;
+    while (true) {
+        try {
+            // Step 1.a.
+            next = callContentFunction(iterator.next, iterator);
+            if (!IsObject(next))
+                ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE);
+        } catch (e) {
+            // Step 1.b.
+            iteratorRecord.done = true;
+
+            // Step 1.c.
+            throw (e);
+        }
+
+        // Step 1.d.
+        if (next.done) {
+            // Step 1.d.i.
+            iteratorRecord.done = true;
+
+            // Step 1.d.ii.
+            return resultCapability.promise;
+        }
+        try {
+            // Step 1.e.
+            nextValue = next.value;
+        } catch (e) {
+            // Step 1.f.
+            iteratorRecord.done = true;
+
+            // Step 1.g.
+            throw e;
+        }
+
+        // Step 1.h.
+        let nextPromise = callContentFunction(C.resolve, C, nextValue);
+
+        // Steps 1.i.
+        callContentFunction(nextPromise.then, nextPromise,
+                            resultCapability.resolve, resultCapability.reject);
+    }
+    assert(false, "Shouldn't reach the end of PerformPromiseRace")
+}
+
+// ES6, 25.4.4.4.
+function Promise_static_reject(r) {
+    // Step 1.
+    let C = this;
+
+    // Step 2.
+    if (!IsObject(C))
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.reject call");
+
+    // Steps 3-4.
+    let promiseCapability = NewPromiseCapability(C);
+
+    // Steps 5-6.
+    callContentFunction(promiseCapability.reject, undefined, r);
+
+    // Step 7.
+    return promiseCapability.promise;
+}
+
+// ES6, 25.4.4.5.
+function Promise_static_resolve(x) {
+    // Step 1.
+    let C = this;
+
+    // Step 2.
+    if (!IsObject(C))
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.resolve call");
+
+    // Step 3.
+    if (IsObject(x) && (IsPromise(x) || IsWrappedPromise(x)) && x.constructor === C)
+        return x;
+
+    // Steps 4-5.
+    let promiseCapability = NewPromiseCapability(C);
+
+    // Steps 6-7.
+    callContentFunction(promiseCapability.resolve, undefined, x);
+
+    // Step 8.
+    return promiseCapability.promise;
+}
+
+//ES6, 25.4.4.6.
+function Promise_static_get_species() {
+    // Step 1.
+    return this;
+}
+_SetCanonicalName(Promise_static_get_species, "get [Symbol.species]");
+
+// ES6, 25.4.5.1.
+function Promise_catch(onRejected) {
+    // Steps 1-2.
+    return callContentFunction(this.then, this, undefined, onRejected);
+}
+
+// ES6, 25.4.5.3.
+function Promise_then(onFulfilled, onRejected) {
+    // Step 1.
+    let promise = this;
+
+    // Step 2.
+    if (!IsObject(promise))
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.prototype.then call");
+
+    let isPromise = IsPromise(promise);
+    let isWrappedPromise = isPromise ? false : IsWrappedPromise(promise);
+    if (!(isPromise || isWrappedPromise))
+        ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Promise", "then", "value");
+
+    // Steps 3-4.
+    let C = SpeciesConstructor(promise, GetBuiltinConstructor('Promise'));
+
+    // Steps 5-6.
+    let resultCapability = NewPromiseCapability(C);
+
+    // Step 7.
+    if (isWrappedPromise) {
+        return callFunction(CallPromiseMethodIfWrapped, promise, onFulfilled, onRejected,
+                            resultCapability.promise, resultCapability.resolve,
+                            resultCapability.reject, "UnwrappedPerformPromiseThen");
+    }
+
+    return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability);
+}
+
+/**
+ * Forwarder used to invoke PerformPromiseThen on an unwrapped Promise, while
+ * wrapping the resolve/reject callbacks into functions that invoke them in
+ * their original compartment. Otherwise, calling them with objects as
+ * arguments would throw if they're wrapped in Xray wrappers.
+ */
+function UnwrappedPerformPromiseThen(onFulfilled, onRejected, promise, resolve, reject) {
+    let resultCapability = {
+        __proto__: PromiseCapabilityRecordProto,
+        promise,
+        resolve(resolution) {
+            return UnsafeCallWrappedFunction(resolve, undefined, resolution);
+        },
+        reject(reason) {
+            return UnsafeCallWrappedFunction(reject, undefined, reason);
+        }
+    };
+    return PerformPromiseThen(this, onFulfilled, onRejected, resultCapability);
+}
+
+/**
+ * Enqueues resolve/reject reactions in the given Promise's reactions lists
+ * in a content-invisible way.
+ *
+ * Used internally to implement DOM functionality.
+ *
+ * Note: the reactions pushed using this function contain a `capabilities`
+ * object whose `promise` field can contain null. That field is only ever used
+ * by devtools, which have to treat these reactions specially.
+ */
+function EnqueuePromiseReactions(promise, dependentPromise, onFulfilled, onRejected) {
+    let isWrappedPromise = false;
+    if (!IsPromise(promise)) {
+        assert(IsWrappedPromise(promise),
+               "EnqueuePromiseReactions must be provided with a possibly wrapped promise");
+        isWrappedPromise = true;
+    }
+
+    assert(dependentPromise === null || IsPromise(dependentPromise),
+           "EnqueuePromiseReactions's dependentPromise argument must be a Promise or null");
+
+    if (isWrappedPromise) {
+        return callFunction(CallPromiseMethodIfWrapped, promise, onFulfilled, onRejected,
+                            dependentPromise, NullFunction, NullFunction,
+                            "UnwrappedPerformPromiseThen");
+    }
+    let capability = {
+        __proto__: PromiseCapabilityRecordProto,
+        promise: dependentPromise,
+        resolve: NullFunction,
+        reject: NullFunction
+    };
+    return PerformPromiseThen(promise, onFulfilled, onRejected, capability);
+}
+
+// ES6, 25.4.5.3.1.
+function PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability) {
+    // Step 1.
+    assert(IsPromise(promise), "Can't call PerformPromiseThen on non-Promise objects");
+
+    // Step 2.
+    assert(IsPromiseCapability(resultCapability), "Invalid promise capability record");
+
+    // Step 3.
+    if (!IsCallable(onFulfilled))
+        onFulfilled = PROMISE_HANDLER_IDENTITY;
+
+    // Step 4.
+    if (!IsCallable(onRejected))
+        onRejected = PROMISE_HANDLER_THROWER;
+
+    // Step 5.
+    let fulfillReaction = {
+        __proto__: PromiseReactionRecordProto,
+        capabilities: resultCapability,
+        handler: onFulfilled
+    };
+
+    // Step 6.
+    let rejectReaction = {
+        __proto__: PromiseReactionRecordProto,
+        capabilities: resultCapability,
+        handler: onRejected
+    };
+
+    // Step 7.
+    let state = GetPromiseState(promise);
+    if (state === PROMISE_STATE_PENDING) {
+        // Step 7.a.
+        let fulfillReactions = UnsafeGetObjectFromReservedSlot(promise,
+                                                               PROMISE_FULFILL_REACTIONS_SLOT);
+        _DefineDataProperty(fulfillReactions, fulfillReactions.length, fulfillReaction);
+
+        // Step 7.b.
+        let rejectReactions = UnsafeGetObjectFromReservedSlot(promise,
+                                                              PROMISE_REJECT_REACTIONS_SLOT);
+        _DefineDataProperty(rejectReactions, rejectReactions.length, rejectReaction);
+    }
+
+    // Step 8.
+    else if (state === PROMISE_STATE_FULFILLED) {
+        // Step 8.a.
+        let value = UnsafeGetReservedSlot(promise, PROMISE_RESULT_SLOT);
+
+        // Step 8.b.
+        EnqueuePromiseReactionJob(fulfillReaction, value);
+    }
+
+    // Step 9.
+    else if (state === PROMISE_STATE_REJECTED) {
+        // Step 9.a.
+        let reason = UnsafeGetReservedSlot(promise, PROMISE_RESULT_SLOT);
+
+        // Step 9.b.
+        EnqueuePromiseReactionJob(rejectReaction, reason);
+    }
+
+    // Step 10.
+    return resultCapability.promise;
+}
+
+/// Utility functions below.
+function IsPromiseReaction(record) {
+    return std_Reflect_getPrototypeOf(record) === PromiseReactionRecordProto;
+}
+
+function IsPromiseCapability(capability) {
+    return std_Reflect_getPrototypeOf(capability) === PromiseCapabilityRecordProto;
+}
+
+function GetPromiseState(promise) {
+    return UnsafeGetInt32FromReservedSlot(promise, PROMISE_STATE_SLOT);
+}
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -52,16 +52,30 @@
 #define ITERATOR_SLOT_ITEM_KIND 2
 // Used for ListIterator.
 #define ITERATOR_SLOT_NEXT_METHOD 2
 
 #define ITEM_KIND_KEY 0
 #define ITEM_KIND_VALUE 1
 #define ITEM_KIND_KEY_AND_VALUE 2
 
+#define PROMISE_STATE_SLOT             0
+#define PROMISE_RESULT_SLOT            1
+#define PROMISE_FULFILL_REACTIONS_SLOT 2
+#define PROMISE_REJECT_REACTIONS_SLOT  3
+#define PROMISE_RESOLVE_FUNCTION_SLOT  4
+#define PROMISE_REJECT_FUNCTION_SLOT   5
+
+#define PROMISE_STATE_PENDING   0
+#define PROMISE_STATE_FULFILLED 1
+#define PROMISE_STATE_REJECTED  2
+
+#define PROMISE_HANDLER_IDENTITY 0
+#define PROMISE_HANDLER_THROWER  1
+
 // NB: keep these in sync with the copy in jsfriendapi.h.
 #define JSITER_OWNONLY    0x8   /* iterate over obj's own properties only */
 #define JSITER_HIDDEN     0x10  /* also enumerate non-enumerable properties */
 #define JSITER_SYMBOLS    0x20  /* also include symbol property keys */
 #define JSITER_SYMBOLSONLY 0x40 /* exclude string property keys */
 
 #define TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED 25
 
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -178,30 +178,41 @@ function SpeciesConstructor(obj, default
     // Step 4.
     if (ctor === undefined)
         return defaultConstructor;
 
     // Step 5.
     if (!IsObject(ctor))
         ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "object's 'constructor' property");
 
-    // Steps 6-7.  We don't yet implement @@species and Symbol.species, so we
-    // don't implement this correctly right now.  Somebody fix this!
-    var s = /* ctor[Symbol.species] */ undefined;
+    // Steps 6-7.
+    var s = ctor[std_species];
 
     // Step 8.
     if (s === undefined || s === null)
         return defaultConstructor;
 
     // Step 9.
     if (IsConstructor(s))
         return s;
 
     // Step 10.
     ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, "@@species property of object's constructor");
 }
 
+function GetTypeError(msg) {
+    try {
+        FUN_APPLY(ThrowTypeError, undefined, arguments);
+    } catch (e) {
+        return e;
+    }
+    assert(false, "the catch block should've returned from this function.");
+}
+
+// To be used when a function is required but calling it shouldn't do anything.
+function NullFunction() {}
+
 /*************************************** Testing functions ***************************************/
 function outer() {
     return function inner() {
         return "foo";
     }
 }
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -515,8 +515,14 @@ MSG_DEF(JSMSG_REINIT_THIS,       0, JSEX
 // Modules
 MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT,        0, JSEXN_SYNTAXERR, "default export cannot be provided by export *")
 MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT,   1, JSEXN_SYNTAXERR, "indirect export '{0}' not found")
 MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 1, JSEXN_SYNTAXERR, "ambiguous indirect export '{0}'")
 MSG_DEF(JSMSG_MISSING_IMPORT,            1, JSEXN_SYNTAXERR, "import '{0}' not found")
 MSG_DEF(JSMSG_AMBIGUOUS_IMPORT,          1, JSEXN_SYNTAXERR, "ambiguous import '{0}'")
 MSG_DEF(JSMSG_MISSING_NAMESPACE_EXPORT,  0, JSEXN_SYNTAXERR, "export not found for namespace")
 MSG_DEF(JSMSG_MISSING_EXPORT,            1, JSEXN_SYNTAXERR, "local binding for export '{0}' not found")
+
+// Promise
+MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF,       0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.")
+MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.")
+MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE,    0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
+MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE,     0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -107,16 +107,21 @@ if CONFIG['ENABLE_ION']:
         'testJitGVN.cpp',
         'testJitMoveEmitterCycles-mips32.cpp',
         'testJitMoveEmitterCycles.cpp',
         'testJitRangeAnalysis.cpp',
         'testJitRegisterSet.cpp',
         'testJitRValueAlloc.cpp',
     ]
 
+if CONFIG['SPIDERMONKEY_PROMISE']:
+    UNIFIED_SOURCES += [
+        'testPromise.cpp',
+    ]
+
 DEFINES['EXPORT_JS_API'] = True
 
 LOCAL_INCLUDES += [
     '!..',
     '..',
 ]
 
 USE_LIBS += [
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testPromise.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsapi-tests/tests.h"
+
+static bool executor_called = false;
+
+static bool
+PromiseExecutor(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 2);
+    MOZ_ASSERT(args[0].toObject().is<JSFunction>());
+    MOZ_ASSERT(args[1].toObject().is<JSFunction>());
+
+    executor_called = true;
+    return true;
+}
+
+static JSObject*
+CreatePromise(JSContext* cx)
+{
+    RootedFunction executor(cx, JS_NewFunction(cx, PromiseExecutor, 2, 0, "executor"));
+    if (!executor)
+        return nullptr;
+    return JS::NewPromiseObject(cx, executor);
+}
+
+BEGIN_TEST(testPromise_NewPromise)
+{
+    RootedObject promise(cx, CreatePromise(cx));
+    CHECK(promise);
+    CHECK(executor_called);
+
+    return true;
+}
+END_TEST(testPromise_NewPromise)
+
+BEGIN_TEST(testPromise_GetPromiseState)
+{
+    RootedObject promise(cx, CreatePromise(cx));
+    if (!promise)
+        return false;
+
+    CHECK(JS::GetPromiseState(promise) == JS::PromiseState::Pending);
+
+    return true;
+}
+END_TEST(testPromise_GetPromiseState)
+
+BEGIN_TEST(testPromise_ResolvePromise)
+{
+    RootedObject promise(cx, CreatePromise(cx));
+    if (!promise)
+        return false;
+
+    RootedValue result(cx);
+    result.setInt32(42);
+    JS::ResolvePromise(cx, promise, result);
+
+    CHECK(JS::GetPromiseState(promise) == JS::PromiseState::Fulfilled);
+
+    return true;
+}
+END_TEST(testPromise_ResolvePromise)
+
+BEGIN_TEST(testPromise_RejectPromise)
+{
+    RootedObject promise(cx, CreatePromise(cx));
+    if (!promise)
+        return false;
+
+    RootedValue result(cx);
+    result.setInt32(42);
+    JS::RejectPromise(cx, promise, result);
+
+    CHECK(JS::GetPromiseState(promise) == JS::PromiseState::Rejected);
+
+    return true;
+}
+END_TEST(testPromise_RejectPromise)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -42,16 +42,17 @@
 #include "jsweakmap.h"
 #include "jswrapper.h"
 
 #include "asmjs/AsmJS.h"
 #include "builtin/AtomicsObject.h"
 #include "builtin/Eval.h"
 #include "builtin/Intl.h"
 #include "builtin/MapObject.h"
+#include "builtin/Promise.h"
 #include "builtin/RegExp.h"
 #include "builtin/SymbolObject.h"
 #ifdef ENABLE_SIMD
 # include "builtin/SIMD.h"
 #endif
 #ifdef ENABLE_BINARYDATA
 # include "builtin/TypedObject.h"
 #endif
@@ -73,16 +74,17 @@
 #include "vm/Debugger.h"
 #include "vm/ErrorObject.h"
 #include "vm/HelperThreads.h"
 #include "vm/Interpreter.h"
 #include "vm/RegExpStatics.h"
 #include "vm/Runtime.h"
 #include "vm/SavedStacks.h"
 #include "vm/ScopeObject.h"
+#include "vm/SelfHosting.h"
 #include "vm/Shape.h"
 #include "vm/StopIterationObject.h"
 #include "vm/StringBuffer.h"
 #include "vm/Symbol.h"
 #include "vm/TypedArrayCommon.h"
 #include "vm/WrapperObject.h"
 #include "vm/Xdr.h"
 
@@ -4625,16 +4627,195 @@ JS_SetInterruptCallback(JSRuntime* rt, J
 }
 
 JS_PUBLIC_API(JSInterruptCallback)
 JS_GetInterruptCallback(JSRuntime* rt)
 {
     return rt->interruptCallback;
 }
 
+/************************************************************************/
+
+/*
+ * Promises.
+ */
+
+JS_PUBLIC_API(void)
+JS::SetEnqueuePromiseJobCallback(JSRuntime* rt, JSEnqueuePromiseJobCallback callback,
+                                 void* data /* = nullptr */)
+{
+    rt->enqueuePromiseJobCallback = callback;
+    rt->enqueuePromiseJobCallbackData = data;
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::NewPromiseObject(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */)
+{
+    MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
+    MOZ_ASSERT(IsCallable(executor));
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+
+    return PromiseObject::create(cx, executor, proto);
+}
+
+JS_PUBLIC_API(bool)
+JS::IsPromiseObject(JS::HandleObject obj)
+{
+    JSObject* object = CheckedUnwrap(obj);
+    return object && object->is<PromiseObject>();
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::GetPromiseConstructor(JSContext* cx)
+{
+    CHECK_REQUEST(cx);
+    Rooted<GlobalObject*> global(cx, cx->global());
+    return GlobalObject::getOrCreatePromiseConstructor(cx, global);
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::GetPromisePrototype(JSContext* cx)
+{
+    CHECK_REQUEST(cx);
+    Rooted<GlobalObject*> global(cx, cx->global());
+    return GlobalObject::getOrCreatePromisePrototype(cx, global);
+}
+
+JS_PUBLIC_API(JS::PromiseState)
+JS::GetPromiseState(JS::HandleObject obj)
+{
+    JSObject* promise = CheckedUnwrap(obj);
+    return promise->as<PromiseObject>().state();
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::CallOriginalPromiseResolve(JSContext* cx, JS::HandleValue resolutionValue)
+{
+    InvokeArgs args(cx);
+    if (!args.init(1))
+        return nullptr;
+    RootedObject promiseCtor(cx, GetPromiseConstructor(cx));
+    if (!promiseCtor)
+        return nullptr;
+    args.setThis(ObjectValue(*promiseCtor));
+    args[0].set(resolutionValue);
+
+    if (!CallSelfHostedFunction(cx, "Promise_static_resolve", args))
+        return nullptr;
+    MOZ_ASSERT(args.rval().isObject());
+    JSObject* obj = &args.rval().toObject();
+    MOZ_ASSERT(obj->is<PromiseObject>());
+    return obj;
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::CallOriginalPromiseReject(JSContext* cx, JS::HandleValue rejectionValue)
+{
+    InvokeArgs args(cx);
+    if (!args.init(1))
+        return nullptr;
+    RootedObject promiseCtor(cx, GetPromiseConstructor(cx));
+    if (!promiseCtor)
+        return nullptr;
+    args.setThis(ObjectValue(*promiseCtor));
+    args[0].set(rejectionValue);
+
+    if (!CallSelfHostedFunction(cx, "Promise_static_reject", args))
+        return nullptr;
+    MOZ_ASSERT(args.rval().isObject());
+    JSObject* obj = &args.rval().toObject();
+    MOZ_ASSERT(obj->is<PromiseObject>());
+    return obj;
+}
+
+JS_PUBLIC_API(bool)
+JS::ResolvePromise(JSContext* cx, JS::HandleObject promise, JS::HandleValue resolutionValue)
+{
+    MOZ_ASSERT(promise->is<PromiseObject>());
+    return promise->as<PromiseObject>().resolve(cx, resolutionValue);
+}
+
+JS_PUBLIC_API(bool)
+JS::RejectPromise(JSContext* cx, JS::HandleObject promise, JS::HandleValue rejectionValue)
+{
+    MOZ_ASSERT(promise->is<PromiseObject>());
+    return promise->as<PromiseObject>().reject(cx, rejectionValue);
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::CallOriginalPromiseThen(JSContext* cx, JS::HandleObject promise,
+                            JS::HandleObject onResolve, JS::HandleObject onReject)
+{
+    MOZ_ASSERT(promise->is<PromiseObject>());
+    MOZ_ASSERT(onResolve == nullptr || IsCallable(onResolve));
+    MOZ_ASSERT(onReject == nullptr || IsCallable(onReject));
+    InvokeArgs args(cx);
+    if (!args.init(2))
+        return nullptr;
+    args.setThis(ObjectValue(*promise));
+    args[0].setObjectOrNull(onResolve);
+    args[1].setObjectOrNull(onReject);
+
+    if (!CallSelfHostedFunction(cx, "Promise_then", args))
+        return nullptr;
+    MOZ_ASSERT(args.rval().isObject());
+    JSObject* obj = &args.rval().toObject();
+    MOZ_ASSERT(obj->is<PromiseObject>());
+    return obj;
+}
+
+JS_PUBLIC_API(bool)
+JS::AddPromiseReactions(JSContext* cx, JS::HandleObject promise,
+                        JS::HandleObject onResolve, JS::HandleObject onReject)
+{
+    MOZ_ASSERT(promise->is<PromiseObject>());
+    MOZ_ASSERT(IsCallable(onResolve));
+    MOZ_ASSERT(IsCallable(onReject));
+    InvokeArgs args(cx);
+    if (!args.init(4))
+        return false;
+    args[0].setObject(*promise);
+    args[1].setNull();
+    args[2].setObject(*onResolve);
+    args[3].setObject(*onReject);
+
+    return js::CallSelfHostedFunction(cx, "EnqueuePromiseReactions", args);
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
+{
+    RootedArrayObject arr(cx, NewDenseFullyAllocatedArray(cx, promises.length()));
+    if (!arr)
+        return nullptr;
+    arr->ensureDenseInitializedLength(cx, 0, promises.length());
+    for (size_t i = 0, len = promises.length(); i < len; i++) {
+#ifdef DEBUG
+        JSObject* obj = promises[i];
+        if (IsWrapper(obj))
+            obj = UncheckedUnwrap(obj);
+        MOZ_ASSERT(obj->is<PromiseObject>());
+#endif
+        arr->setDenseElement(i, ObjectValue(*promises[i]));
+    }
+
+    InvokeArgs args(cx);
+    if (!args.init(1))
+        return nullptr;
+    args[0].setObject(*arr);
+
+    if (!js::CallSelfHostedFunction(cx, "GetWaitForAllPromise", args))
+        return nullptr;
+    MOZ_ASSERT(args.rval().isObject());
+    JSObject* obj = &args.rval().toObject();
+    MOZ_ASSERT(obj->is<PromiseObject>());
+    return obj;
+}
+
 JS_PUBLIC_API(void)
 JS_RequestInterruptCallback(JSRuntime* rt)
 {
     rt->requestInterrupt(JSRuntime::RequestInterruptUrgent);
 }
 
 JS_PUBLIC_API(bool)
 JS_IsRunning(JSContext* cx)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -605,16 +605,19 @@ typedef void
 (* JSWeakPointerZoneGroupCallback)(JSRuntime* rt, void* data);
 
 typedef void
 (* JSWeakPointerCompartmentCallback)(JSRuntime* rt, JSCompartment* comp, void* data);
 
 typedef bool
 (* JSInterruptCallback)(JSContext* cx);
 
+typedef bool
+(* JSEnqueuePromiseJobCallback)(JSContext* cx, JS::HandleObject job, void* data);
+
 typedef void
 (* JSErrorReporter)(JSContext* cx, const char* message, JSErrorReport* report);
 
 /**
  * Possible exception types. These types are part of a JSErrorFormatString
  * structure. They define which error to throw in case of a runtime error.
  * JSEXN_NONE marks an unthrowable error.
  */
@@ -4283,16 +4286,143 @@ extern JS_PUBLIC_API(JSInterruptCallback
 JS_SetInterruptCallback(JSRuntime* rt, JSInterruptCallback callback);
 
 extern JS_PUBLIC_API(JSInterruptCallback)
 JS_GetInterruptCallback(JSRuntime* rt);
 
 extern JS_PUBLIC_API(void)
 JS_RequestInterruptCallback(JSRuntime* rt);
 
+namespace JS {
+
+/**
+ * Sets the callback that's invoked whenever a Promise job should be enqeued.
+ *
+ * SpiderMonkey doesn't schedule Promise resolution jobs itself; instead,
+ * using this function the embedding can provide a callback to do that
+ * scheduling. The provided `callback` is invoked with the promise job
+ * and the `data` pointer passed here as arguments.
+ */
+extern JS_PUBLIC_API(void)
+SetEnqueuePromiseJobCallback(JSRuntime* rt, JSEnqueuePromiseJobCallback callback,
+                             void* data = nullptr);
+
+/**
+ * Returns a new instance of the Promise builtin class in the current
+ * compartment, with the right slot layout. If a `proto` is passed, that gets
+ * set as the instance's [[Prototype]] instead of the original value of
+ * `Promise.prototype`.
+ */
+extern JS_PUBLIC_API(JSObject*)
+NewPromiseObject(JSContext* cx, JS::HandleObject executor, JS::HandleObject proto = nullptr);
+
+/**
+ * Returns true if the given object is an unwrapped PromiseObject, false
+ * otherwise.
+ */
+extern JS_PUBLIC_API(bool)
+IsPromiseObject(JS::HandleObject obj);
+
+/**
+ * Returns the current compartment's original Promise constructor.
+ */
+extern JS_PUBLIC_API(JSObject*)
+GetPromiseConstructor(JSContext* cx);
+
+/**
+ * Returns the current compartment's original Promise.prototype.
+ */
+extern JS_PUBLIC_API(JSObject*)
+GetPromisePrototype(JSContext* cx);
+
+// Keep this in sync with the PROMISE_STATE defines in SelfHostingDefines.h.
+enum class PromiseState {
+    Pending,
+    Fulfilled,
+    Rejected
+};
+
+extern JS_PUBLIC_API(PromiseState)
+GetPromiseState(JS::HandleObject promise);
+
+/**
+ * Calls the current compartment's original Promise.resolve on the original
+ * Promise constructor, with `resolutionValue` passed as an argument.
+ */
+extern JS_PUBLIC_API(JSObject*)
+CallOriginalPromiseResolve(JSContext* cx, JS::HandleValue resolutionValue);
+
+/**
+ * Calls the current compartment's original Promise.reject on the original
+ * Promise constructor, with `resolutionValue` passed as an argument.
+ */
+extern JS_PUBLIC_API(JSObject*)
+CallOriginalPromiseReject(JSContext* cx, JS::HandleValue rejectionValue);
+
+/**
+ * Resolves the given `promise` with the given `resolutionValue`.
+ *
+ * Calls the `resolve` function that was passed to the executor function when
+ * the Promise was created.
+ */
+extern JS_PUBLIC_API(bool)
+ResolvePromise(JSContext* cx, JS::HandleObject promise, JS::HandleValue resolutionValue);
+
+/**
+ * Rejects the given `promise` with the given `rejectionValue`.
+ *
+ * Calls the `reject` function that was passed to the executor function when
+ * the Promise was created.
+ */
+extern JS_PUBLIC_API(bool)
+RejectPromise(JSContext* cx, JS::HandleObject promise, JS::HandleValue rejectionValue);
+
+/**
+ * Calls the current compartment's original Promise.prototype.then on the
+ * given `promise`, with `onResolve` and `onReject` passed as arguments.
+ *
+ * Asserts if the passed-in `promise` object isn't an unwrapped instance of
+ * `Promise` or a subclass or `onResolve` and `onReject` aren't both either
+ * `nullptr` or callable objects.
+ */
+extern JS_PUBLIC_API(JSObject*)
+CallOriginalPromiseThen(JSContext* cx, JS::HandleObject promise,
+                        JS::HandleObject onResolve, JS::HandleObject onReject);
+
+/**
+ * Unforgeable, optimized version of the JS builtin Promise.prototype.then.
+ *
+ * Takes a Promise instance and `onResolve`, `onReject` callables to enqueue
+ * as reactions for that promise. In difference to Promise.prototype.then,
+ * this doesn't create and return a new Promise instance.
+ *
+ * Asserts if the passed-in `promise` object isn't an unwrapped instance of
+ * `Promise` or a subclass or `onResolve` and `onReject` aren't both callable
+ * objects.
+ */
+extern JS_PUBLIC_API(bool)
+AddPromiseReactions(JSContext* cx, JS::HandleObject promise,
+                    JS::HandleObject onResolve, JS::HandleObject onReject);
+
+/**
+ * Unforgeable version of the JS builtin Promise.all.
+ *
+ * Takes an AutoObjectVector of Promise objects and returns a promise that's
+ * resolved with an array of resolution values when all those promises ahve
+ * been resolved, or rejected with the rejection value of the first rejected
+ * promise.
+ *
+ * Asserts if the array isn't dense or one of the entries isn't an unwrapped
+ * instance of Promise or a subclass.
+ */
+extern JS_PUBLIC_API(JSObject*)
+GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises);
+
+} // namespace JS
+
 extern JS_PUBLIC_API(bool)
 JS_IsRunning(JSContext* cx);
 
 /*
  * Saving and restoring frame chains.
  *
  * These two functions are used to set aside cx's call stack while that stack
  * is inactive. After a call to JS_SaveFrameChain, it looks as if there is no
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -14,16 +14,17 @@
 #include "jscompartment.h"
 #include "jsgc.h"
 #include "jsobj.h"
 #include "jsprf.h"
 #include "jswatchpoint.h"
 #include "jsweakmap.h"
 #include "jswrapper.h"
 
+#include "builtin/Promise.h"
 #include "builtin/TestingFunctions.h"
 #include "js/Proxy.h"
 #include "proxy/DeadObjectProxy.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/Time.h"
 #include "vm/WrapperObject.h"
 
 #include "jsobjinlines.h"
@@ -281,16 +282,18 @@ js::GetBuiltinClass(JSContext* cx, Handl
     else if (obj->is<SharedArrayBufferObject>())
         *classValue = ESClass_SharedArrayBuffer;
     else if (obj->is<DateObject>())
         *classValue = ESClass_Date;
     else if (obj->is<SetObject>())
         *classValue = ESClass_Set;
     else if (obj->is<MapObject>())
         *classValue = ESClass_Map;
+    else if (obj->is<PromiseObject>())
+        *classValue = ESClass_Promise;
     else
         *classValue = ESClass_Other;
 
     return true;
 }
 
 JS_FRIEND_API(const char*)
 js::ObjectClassName(JSContext* cx, HandleObject obj)
--- a/js/src/jsprototypes.h
+++ b/js/src/jsprototypes.h
@@ -56,16 +56,22 @@
 #endif
 
 #ifdef ENABLE_SHARED_ARRAY_BUFFER
 #define IF_SAB(real,imaginary) real
 #else
 #define IF_SAB(real,imaginary) imaginary
 #endif
 
+#ifdef SPIDERMONKEY_PROMISE
+#define IF_PROMISE(real,imaginary) real
+#else
+#define IF_PROMISE(real,imaginary) imaginary
+#endif
+
 #define JS_FOR_PROTOTYPES(real,imaginary) \
     imaginary(Null,              0,     InitNullClass,          dummy) \
     real(Object,                 1,     InitViaClassSpec,       OCLASP(Plain)) \
     real(Function,               2,     InitViaClassSpec,       &JSFunction::class_) \
     real(Array,                  3,     InitViaClassSpec,       OCLASP(Array)) \
     real(Boolean,                4,     InitBooleanClass,       OCLASP(Boolean)) \
     real(JSON,                   5,     InitJSONClass,          CLASP(JSON)) \
     real(Date,                   6,     InitViaClassSpec,       OCLASP(Date)) \
@@ -105,12 +111,13 @@ IF_INTL(real,imaginary) (Intl,          
 IF_BDATA(real,imaginary)(TypedObject,           40,     InitTypedObjectModuleObject,   OCLASP(TypedObjectModule)) \
     real(Reflect,               41,     InitReflect,            nullptr) \
 IF_SIMD(real,imaginary)(SIMD,                   42,     InitSimdClass, OCLASP(Simd)) \
     real(WeakSet,               43,     InitWeakSetClass,       OCLASP(WeakSet)) \
     real(TypedArray,            44,     InitViaClassSpec,       &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \
 IF_SAB(real,imaginary)(Atomics, 45,     InitAtomicsClass, OCLASP(Atomics)) \
     real(SavedFrame,            46,     InitViaClassSpec,       &js::SavedFrame::class_) \
     real(Wasm,                  47,     InitWasmClass,          CLASP(Wasm)) \
+IF_PROMISE(real,imaginary)(Promise,             48,     InitViaClassSpec, OCLASP(Promise)) \
 
 #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)
 
 #endif /* jsprototypes_h */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -160,16 +160,17 @@ UNIFIED_SOURCES += [
     'asmjs/WasmTypes.cpp',
     'builtin/AtomicsObject.cpp',
     'builtin/Eval.cpp',
     'builtin/Intl.cpp',
     'builtin/MapObject.cpp',
     'builtin/ModuleObject.cpp',
     'builtin/Object.cpp',
     'builtin/Profilers.cpp',
+    'builtin/Promise.cpp',
     'builtin/Reflect.cpp',
     'builtin/ReflectParse.cpp',
     'builtin/SIMD.cpp',
     'builtin/SymbolObject.cpp',
     'builtin/TestingFunctions.cpp',
     'builtin/TypedObject.cpp',
     'builtin/WeakMapObject.cpp',
     'builtin/WeakSetObject.cpp',
@@ -728,16 +729,19 @@ selfhosted.inputs = [
     'builtin/String.js',
     'builtin/Set.js',
     'builtin/Sorting.js',
     'builtin/TypedArray.js',
     'builtin/TypedObject.js',
     'builtin/WeakSet.js'
 ]
 
+if CONFIG['SPIDERMONKEY_PROMISE']:
+    selfhosted.inputs += ['builtin/Promise.js']
+
 if CONFIG['JS_HAS_CTYPES']:
     if CONFIG['MOZ_SYSTEM_FFI']:
         CXXFLAGS += CONFIG['MOZ_FFI_CFLAGS']
     else:
         # Windows needs this to be linked with a static library.
         DEFINES['FFI_BUILDING'] = True
 
 # Suppress warnings in third-party code.
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -134,28 +134,31 @@ static const size_t gMaxStackSize = 128 
 static const double MAX_TIMEOUT_INTERVAL = 1800.0;
 
 #ifdef NIGHTLY_BUILD
 # define SHARED_MEMORY_DEFAULT 1
 #else
 # define SHARED_MEMORY_DEFAULT 0
 #endif
 
+using JobQueue = GCVector<JSObject*, 0, SystemAllocPolicy>;
+
 // Per-runtime shell state.
 struct ShellRuntime
 {
     explicit ShellRuntime(JSRuntime* rt);
 
     bool isWorker;
     double timeoutInterval;
     Atomic<bool> serviceInterrupt;
     Atomic<bool> haveInterruptFunc;
     JS::PersistentRootedValue interruptFunc;
     bool lastWarningEnabled;
     JS::PersistentRootedValue lastWarning;
+    JS::PersistentRooted<JobQueue> jobQueue;
 
     /*
      * Watchdog thread state.
      */
     PRLock* watchdogLock;
     PRCondVar* watchdogWakeup;
     PRThread* watchdogThread;
     bool watchdogHasTimeout;
@@ -613,16 +616,58 @@ RunModule(JSContext* cx, const char* fil
     RootedValue value(cx);
     if (!JS_CallFunction(cx, loaderObj, importFun, args, &value)) {
         sr->exitCode = EXITCODE_RUNTIME_ERROR;
         return;
     }
 }
 
 static bool
+ShellEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject job, void* data)
+{
+    ShellRuntime* sr = GetShellRuntime(cx);
+    MOZ_ASSERT(job);
+    return sr->jobQueue.append(job);
+}
+
+static bool
+DrainJobQueue(JSContext* cx)
+{
+    ShellRuntime* sr = GetShellRuntime(cx);
+    if (sr->quitting)
+        return true;
+    RootedObject job(cx);
+    JS::HandleValueArray args(JS::HandleValueArray::empty());
+    RootedValue rval(cx);
+    // Execute jobs in a loop until we've reached the end of the queue.
+    // Since executing a job can trigger enqueuing of additional jobs,
+    // it's crucial to re-check the queue length during each iteration.
+    for (size_t i = 0; i < sr->jobQueue.length(); i++) {
+        job = sr->jobQueue[i];
+        AutoCompartment ac(cx, job);
+        if (!JS::Call(cx, UndefinedHandleValue, job, args, &rval))
+            JS_ReportPendingException(cx);
+        sr->jobQueue[i].set(nullptr);
+    }
+    sr->jobQueue.clear();
+    return true;
+}
+
+static bool
+DrainJobQueue(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (!DrainJobQueue(cx))
+        return false;
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
 EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
              int lineno, bool compileOnly)
 {
     // Eval.
     JS::CompileOptions options(cx);
     options.setIntroductionType("js shell interactive")
            .setUTF8(true)
            .setIsRunOnce(true)
@@ -708,16 +753,19 @@ ReadEvalPrintLoop(JSContext* cx, FILE* i
         // be used. This behavior is _only_ acceptable in the context of the repl.
         if (JS::ForceLexicalInitialization(cx, globalLexical) && gErrFile->isOpen()) {
             fputs("Warning: According to the standard, after the above exception,\n"
                   "Warning: the global bindings should be permanently uninitialized.\n"
                   "Warning: We have non-standard-ly initialized them to `undefined`"
                   "for you.\nWarning: This nicety only happens in the JS shell.\n",
                   stderr);
         }
+
+        DrainJobQueue(cx);
+
     } while (!hitEOF && !sr->quitting);
 
     if (gOutFile->isOpen())
         fprintf(gOutFile->fp, "\n");
 }
 
 enum FileKind
 {
@@ -860,16 +908,55 @@ CreateMappedArrayBuffer(JSContext* cx, u
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
+AddPromiseReactions(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() != 3) {
+        JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
+                             args.length() < 3 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
+                             "addPromiseReactions");
+        return false;
+    }
+
+    RootedObject promise(cx);
+    if (args[0].isObject())
+        promise = &args[0].toObject();
+
+    if (!promise || !JS::IsPromiseObject(promise)) {
+        JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
+                             JSSMSG_INVALID_ARGS, "addPromiseReactions");
+        return false;
+    }
+
+    RootedObject onResolve(cx);
+    if (args[1].isObject())
+        onResolve = &args[1].toObject();
+
+    RootedObject onReject(cx);
+    if (args[2].isObject())
+        onReject = &args[2].toObject();
+
+    if (!onResolve || !onResolve->is<JSFunction>() || !onReject || !onReject->is<JSFunction>()) {
+        JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
+                             JSSMSG_INVALID_ARGS, "addPromiseReactions");
+        return false;
+    }
+
+    return JS::AddPromiseReactions(cx, promise, onResolve, onReject);
+}
+
+static bool
 Options(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JS::RuntimeOptions oldRuntimeOptions = JS::RuntimeOptionsRef(cx);
     for (unsigned i = 0; i < args.length(); i++) {
         JSString* str = JS::ToString(cx, args[i]);
         if (!str)
@@ -2820,16 +2907,19 @@ WorkerMain(void* arg)
 
     JSContext* cx = NewContext(rt);
     if (!cx) {
         JS_DestroyRuntime(rt);
         js_delete(input);
         return;
     }
 
+    sr->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
+    JS::SetEnqueuePromiseJobCallback(rt, ShellEnqueuePromiseJobCallback);
+
     JS::SetLargeAllocationFailureCallback(rt, my_LargeAllocFailCallback, (void*)cx);
 
     do {
         JSAutoRequest ar(cx);
 
         JS::CompartmentOptions compartmentOptions;
         SetStandardCompartmentOptions(compartmentOptions);
         RootedObject global(cx, NewGlobalObject(cx, compartmentOptions, nullptr));
@@ -2846,16 +2936,19 @@ WorkerMain(void* arg)
         if (!JS::Compile(cx, options, input->chars, input->length, &script))
             break;
         RootedValue result(cx);
         JS_ExecuteScript(cx, script, &result);
     } while (0);
 
     JS::SetLargeAllocationFailureCallback(rt, nullptr, nullptr);
 
+    JS::SetEnqueuePromiseJobCallback(rt, nullptr);
+    sr->jobQueue.reset();
+
     DestroyContext(cx, false);
 
     KillWatchdog(rt);
 
     JS_DestroyRuntime(rt);
 
     js_delete(input);
 }
@@ -5427,16 +5520,20 @@ static const JSFunctionSpecWithHelp shel
 "         principals of ~0 subsumes all other principals. The absence of a\n"
 "         principal is treated as if its bits were 0xffff, for subsumption\n"
 "         purposes. If this property is omitted, supply no principal."),
 
     JS_FN_HELP("createMappedArrayBuffer", CreateMappedArrayBuffer, 1, 0,
 "createMappedArrayBuffer(filename, [offset, [size]])",
 "  Create an array buffer that mmaps the given file."),
 
+    JS_FN_HELP("addPromiseReactions", AddPromiseReactions, 3, 0,
+"addPromiseReactions(promise, onResolve, onReject)",
+"  Calls the JS::AddPromiseReactions JSAPI function with the given arguments."),
+
     JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0,
 "getMaxArgs()",
 "  Return the maximum number of supported args for a call."),
 
     JS_FN_HELP("objectEmulatingUndefined", ObjectEmulatingUndefined, 0, 0,
 "objectEmulatingUndefined()",
 "  Return a new object obj for which typeof obj === \"undefined\", obj == null\n"
 "  and obj == undefined (and vice versa for !=), and ToBoolean(obj) === false.\n"),
@@ -5500,16 +5597,21 @@ static const JSFunctionSpecWithHelp shel
 "{ eval }: Apply JS::Evaluate to |params.eval|.\n"
 "\n"
 "The return value is an array of strings, with one element for each\n"
 "JavaScript invocation that occurred as a result of the given\n"
 "operation. Each element is the name of the function invoked, or the\n"
 "string 'eval:FILENAME' if the code was invoked by 'eval' or something\n"
 "similar.\n"),
 
+    JS_FN_HELP("drainJobQueue", DrainJobQueue, 0, 0,
+"drainJobQueue()",
+"Take jobs from the shell's job queue in FIFO order and run them until the\n"
+"queue is empty.\n"),
+
     JS_FS_HELP_END
 };
 
 static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
     JS_FN_HELP("clone", Clone, 1, 0,
 "clone(fun[, scope])",
 "  Clone function object."),
 
@@ -6608,16 +6710,18 @@ ProcessArgs(JSContext* cx, OptionParser*
 
     /* The |script| argument is processed after all options. */
     if (const char* path = op->getStringArg("script")) {
         Process(cx, path, false);
         if (sr->exitCode)
             return sr->exitCode;
     }
 
+    DrainJobQueue(cx);
+
     if (op->getBoolOption('i'))
         Process(cx, nullptr, true);
 
     return sr->exitCode ? sr->exitCode : EXIT_SUCCESS;
 }
 
 static bool
 SetRuntimeOptions(JSRuntime* rt, const OptionParser& op)
@@ -7280,16 +7384,19 @@ main(int argc, char** argv, char** envp)
 
     if (!InitWatchdog(rt))
         return 1;
 
     cx = NewContext(rt);
     if (!cx)
         return 1;
 
+    sr->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
+    JS::SetEnqueuePromiseJobCallback(rt, ShellEnqueuePromiseJobCallback);
+
     JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
 
     JS::SetLargeAllocationFailureCallback(rt, my_LargeAllocFailCallback, (void*)cx);
 
     // Set some parameters to allow incremental GC in low memory conditions,
     // as is done for the browser, except in more-deterministic builds or when
     // disabled by command line options.
 #ifndef JS_MORE_DETERMINISTIC
@@ -7306,16 +7413,19 @@ main(int argc, char** argv, char** envp)
 
 #ifdef DEBUG
     if (OOM_printAllocationCount)
         printf("OOM max count: %" PRIu64 "\n", js::oom::counter);
 #endif
 
     JS::SetLargeAllocationFailureCallback(rt, nullptr, nullptr);
 
+    JS::SetEnqueuePromiseJobCallback(rt, nullptr);
+    sr->jobQueue.reset();
+
     DestroyContext(cx, true);
 
     KillWatchdog(rt);
 
     KillWorkerThreads();
 
     DestructSharedArrayBufferMailbox();
 
--- a/js/src/tests/ecma_6/Class/extendBuiltinConstructors.js
+++ b/js/src/tests/ecma_6/Class/extendBuiltinConstructors.js
@@ -95,14 +95,17 @@ testBuiltinTypedArrays();
 testBuiltin(DataView, new ArrayBuffer());
 testBuiltin(DataView, new (newGlobal().ArrayBuffer)());
 testBuiltin(String);
 testBuiltin(Array);
 testBuiltin(Array, 15);
 testBuiltin(Array, 3.0);
 testBuiltin(Array, "non-length one-arg");
 testBuiltin(Array, 5, 10, 15, "these are elements");
+// More Promise subclassing tests can be found in ecma_6/Promise/promise-subclassing.js
+if (typeof Promise !== "undefined")
+    testBuiltin(Promise, _=>{});
 
 if (this.SharedArrayBuffer)
     testBuiltin(SharedArrayBuffer);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Promise/enqueue-promise-reactions.js
@@ -0,0 +1,43 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs getSelfHostedValue and drainJobQueue
+
+if (!this.Promise) {
+    this.reportCompare && reportCompare(true,true);
+    quit(0);
+}
+
+function onResolved(val) {
+    result = 'resolved with ' + val;
+}
+
+function onRejected(val) {
+    result = 'rejected with ' + val;
+}
+
+// Replacing `Promise#then` shouldn't affect addPromiseReactions.
+Promise.prototype.then = 1;
+
+// Replacing Promise@@species shouldn't affect addPromiseReactions.
+Promise[Symbol.species] = function(){};
+
+// Replacing `Promise` shouldn't affect addPromiseReactions.
+let PromiseCtor = Promise;
+Promise = {};
+
+let result;
+let res;
+let rej;
+let p = new PromiseCtor(function(res_, rej_) { res = res_; rej = rej_; });
+
+addPromiseReactions(p, onResolved, onRejected);
+res('foo');
+drainJobQueue();
+assertEq(result, 'resolved with foo')
+
+p = new PromiseCtor(function(res_, rej_) { res = res_; rej = rej_; });
+
+addPromiseReactions(p, onResolved, onRejected);
+rej('bar');
+drainJobQueue();
+assertEq(result, 'rejected with bar');
+
+this.reportCompare && reportCompare(true,true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Promise/get-wait-for-all-promise.js
@@ -0,0 +1,60 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs getSelfHostedValue and drainJobQueue
+
+if (!this.Promise) {
+    this.reportCompare && reportCompare(true,true);
+    quit(0);
+}
+
+let GetWaitForAllPromise = getSelfHostedValue('GetWaitForAllPromise');
+
+function onResolved(val) {
+    result = 'resolved with ' + val;
+}
+
+function onRejected(val) {
+    result = 'rejected with ' + val;
+}
+
+// Replacing `Promise#then` shouldn't affect GetWaitForAllPromise.
+let originalThen = Promise.prototype.then;
+Promise.prototype.then = 1;
+
+// Replacing Promise[@@species] shouldn't affect GetWaitForAllPromise.
+Promise[Symbol.species] = function(){};
+
+// Replacing `Promise` shouldn't affect GetWaitForAllPromise.
+let PromiseCtor = Promise;
+Promise = {};
+
+// Replacing Array[@@iterator] shouldn't affect GetWaitForAllPromise.
+Array.prototype[Symbol.iterator] = function(){};
+
+let resolveFunctions = [];
+let rejectFunctions = [];
+let promises = [];
+for (let i = 0; i < 3; i++) {
+    let p = new PromiseCtor(function(res_, rej_) {
+        resolveFunctions.push(res_);
+        rejectFunctions.push(rej_);
+    });
+    promises.push(p);
+}
+
+let allPromise = GetWaitForAllPromise(promises);
+let then = originalThen.call(allPromise, onResolved, onRejected);
+
+resolveFunctions.forEach((fun, i)=>fun(i));
+drainJobQueue();
+
+assertEq(result, 'resolved with 0,1,2');
+
+//Empty lists result in a promise resolved with an empty array.
+result = undefined;
+originalThen.call(GetWaitForAllPromise([]), v=>(result = v));
+
+drainJobQueue();
+
+assertEq(result instanceof Array, true);
+assertEq(result.length, 0);
+
+this.reportCompare && reportCompare(true,true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Promise/promise-all.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+if (!this.Promise) {
+    this.reportCompare && reportCompare(true,true);
+    quit(0);
+}
+
+let results = [];
+
+let p1 = new Promise(res=>res('result'))
+  .then(val=>{results.push('then ' + val); return 'first then rval';})
+  .then(val=>{results.push('chained then with val: ' + val); return 'p1 then, then'});
+
+let p2 = new Promise((res, rej)=>rej('rejection'))
+  .catch(val=> {results.push('catch ' + val); return results.length;})
+  .then(val=>{results.push('then after catch with val: ' + val); return 'p2 catch, then'},
+        val=>{throw new Error("mustn't be called")});
+
+Promise.all([p1, p2]).then(res => results.push(res + ''));
+
+drainJobQueue();
+
+assertEq(results.length, 5);
+assertEq(results[0], 'then result');
+assertEq(results[1], 'catch rejection');
+assertEq(results[2], 'chained then with val: first then rval');
+assertEq(results[3], 'then after catch with val: 2');
+assertEq(results[4], 'p1 then, then,p2 catch, then');
+
+this.reportCompare && reportCompare(true,true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Promise/promise-basics.js
@@ -0,0 +1,60 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+if (!this.Promise) {
+    this.reportCompare && reportCompare(0, 0, "ok");
+    quit(0);
+}
+
+let results = [];
+
+new Promise(res=>res('result'))
+  .then(val=>{results.push('then ' + val); return 'first then rval';})
+  .then(val=>results.push('chained then with val: ' + val));
+
+new Promise((res, rej)=>rej('rejection'))
+  .catch(val=>{results.push('catch ' + val); return results.length;})
+  .then(val=>results.push('then after catch with val: ' + val),
+        val=>{throw new Error("mustn't be called")});
+
+drainJobQueue();
+
+assertEq(results.length, 4);
+assertEq(results[0], 'then result');
+assertEq(results[1], 'catch rejection');
+assertEq(results[2], 'chained then with val: first then rval');
+assertEq(results[3], 'then after catch with val: 2');
+
+function callback() {}
+
+// Calling the executor function with content functions shouldn't assert:
+Promise.resolve.call(function(exec) { exec(callback, callback); });
+Promise.reject.call(function(exec) { exec(callback, callback); });
+Promise.all.call(function(exec) { exec(callback, callback); });
+Promise.race.call(function(exec) { exec(callback, callback); });
+
+// These should throw:
+var hasThrown = false;
+try {
+    // Calling the executor function twice, providing a resolve callback both times.
+    Promise.resolve.call(function(executor) {
+        executor(callback, undefined);
+        executor(callback, callback);
+    });
+} catch (e) {
+    hasThrown = true;
+}
+assertEq(hasThrown, true);
+
+var hasThrown = false;
+try {
+    // Calling the executor function twice, providing a reject callback both times.
+    Promise.resolve.call(function(executor) {
+        executor(undefined, callback);
+        executor(callback, callback);
+    });
+} catch (e) {
+    hasThrown = true;
+}
+assertEq(hasThrown, true);
+
+this.reportCompare && reportCompare(0, 0, "ok");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Promise/promise-species.js
@@ -0,0 +1,13 @@
+if (!this.Promise) {
+    reportCompare(true,true);
+    quit(0);
+}
+
+assertEq(Promise[Symbol.species], Promise);
+let prop = Object.getOwnPropertyDescriptor(Promise, Symbol.species);
+assertEq('get' in prop, true);
+assertEq(typeof prop.get, 'function');
+assertEq('set' in prop, true);
+assertEq(prop.set, undefined);
+
+reportCompare(0, 0, "ok");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Promise/promise-subclassing.js
@@ -0,0 +1,67 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+if (!this.Promise) {
+    this.reportCompare && reportCompare(true,true);
+    quit(0);
+}
+
+let results = [];
+
+class SubPromise extends Promise {
+  constructor(executor) {
+    results.push('SubPromise ctor called');
+    super(executor);
+  }
+  then(res, rej) {
+    results.push('SubPromise#then called');
+    return intermediatePromise = super.then(res, rej);
+  }
+}
+
+let subPromise = new SubPromise(function(res, rej) {
+  results.push('SubPromise ctor called executor');
+  res('result');
+});
+
+let intermediatePromise;
+let allSubPromise = SubPromise.all([subPromise]);
+
+assertEq(subPromise instanceof SubPromise, true);
+assertEq(allSubPromise instanceof SubPromise, true);
+assertEq(intermediatePromise instanceof SubPromise, true);
+
+expected = [
+'SubPromise ctor called',
+'SubPromise ctor called executor',
+'SubPromise ctor called',
+'SubPromise#then called',
+'SubPromise ctor called',
+];
+
+assertEq(results.length, expected.length);
+expected.forEach((expected,i) => assertEq(results[i], expected));
+
+subPromise.then(val=>results.push('subPromise.then with val ' + val));
+allSubPromise.then(val=>results.push('allSubPromise.then with val ' + val));
+
+expected.forEach((expected,i) => assertEq(results[i], expected));
+expected = expected.concat([
+'SubPromise#then called',
+'SubPromise ctor called',
+'SubPromise#then called',
+'SubPromise ctor called',
+]);
+
+assertEq(results.length, expected.length);
+expected.forEach((expected,i) => assertEq(results[i], expected));
+
+drainJobQueue();
+
+expected = expected.concat([
+'subPromise.then with val result',
+'allSubPromise.then with val result',
+]);
+
+assertEq(results.length, expected.length);
+
+this.reportCompare && reportCompare(0, 0, "ok");
new file mode 100644
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -52,16 +52,17 @@
     macro(columnNumber, columnNumber, "columnNumber") \
     macro(comma, comma, ",") \
     macro(compare, compare, "compare") \
     macro(configurable, configurable, "configurable") \
     macro(construct, construct, "construct") \
     macro(constructor, constructor, "constructor") \
     macro(ConvertAndCopyTo, ConvertAndCopyTo, "ConvertAndCopyTo") \
     macro(count, count, "count") \
+    macro(CreateResolvingFunctions, CreateResolvingFunctions, "CreateResolvingFunctions") \
     macro(currency, currency, "currency") \
     macro(currencyDisplay, currencyDisplay, "currencyDisplay") \
     macro(DateTimeFormat, DateTimeFormat, "DateTimeFormat") \
     macro(DateTimeFormatFormatGet, DateTimeFormatFormatGet, "Intl_DateTimeFormat_format_get") \
     macro(DateTimeFormatFormatToPartsGet, DateTimeFormatFormatToPartsGet, "Intl_DateTimeFormat_formatToParts_get") \
     macro(day, day, "day") \
     macro(dayperiod, dayperiod, "dayperiod") \
     macro(decodeURI, decodeURI, "decodeURI") \
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -20,16 +20,21 @@
 #include "builtin/Eval.h"
 #if EXPOSE_INTL_API
 # include "builtin/Intl.h"
 #endif
 #include "builtin/MapObject.h"
 #include "builtin/ModuleObject.h"
 #include "builtin/Object.h"
 #include "builtin/RegExp.h"
+
+#ifdef NIGHTLY_BUILD
+#include "builtin/Promise.h"
+#endif
+
 #include "builtin/SelfHostingDefines.h"
 #include "builtin/SymbolObject.h"
 #include "builtin/TypedObject.h"
 #include "builtin/WeakMapObject.h"
 #include "builtin/WeakSetObject.h"
 #include "vm/Debugger.h"
 #include "vm/HelperThreads.h"
 #include "vm/PIC.h"
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -385,16 +385,22 @@ class GlobalObject : public NativeObject
     }
 
     static NativeObject* getOrCreateSymbolPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         if (!ensureConstructor(cx, global, JSProto_Symbol))
             return nullptr;
         return &global->getPrototype(JSProto_Symbol).toObject().as<NativeObject>();
     }
 
+    static NativeObject* getOrCreatePromisePrototype(JSContext* cx, Handle<GlobalObject*> global) {
+        if (!ensureConstructor(cx, global, JSProto_Promise))
+            return nullptr;
+        return &global->getPrototype(JSProto_Promise).toObject().as<NativeObject>();
+    }
+
     static NativeObject* getOrCreateRegExpPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         if (!ensureConstructor(cx, global, JSProto_RegExp))
             return nullptr;
         return &global->getPrototype(JSProto_RegExp).toObject().as<NativeObject>();
     }
 
     JSObject* maybeGetRegExpPrototype() {
         if (regexpClassInitialized())
@@ -585,16 +591,23 @@ class GlobalObject : public NativeObject
 
     JSObject* getOrCreateDataViewPrototype(JSContext* cx) {
         RootedGlobalObject self(cx, this);
         if (!ensureConstructor(cx, self, JSProto_DataView))
             return nullptr;
         return &self->getPrototype(JSProto_DataView).toObject();
     }
 
+    static JSFunction*
+    getOrCreatePromiseConstructor(JSContext* cx, Handle<GlobalObject*> global) {
+        if (!ensureConstructor(cx, global, JSProto_Promise))
+            return nullptr;
+        return &global->getConstructor(JSProto_Promise).toObject().as<JSFunction>();
+    }
+
     static NativeObject* getIntrinsicsHolder(JSContext* cx, Handle<GlobalObject*> global);
 
     bool maybeExistingIntrinsicValue(PropertyName* name, Value* vp) {
         Value slot = getReservedSlot(INTRINSICS);
         // If we're in the self-hosting compartment itself, the
         // intrinsics-holder isn't initialized at this point.
         if (slot.isUndefined()) {
             *vp = UndefinedValue();
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -146,16 +146,18 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
 #ifdef DEBUG
     updateChildRuntimeCount(parentRuntime),
 #endif
     interrupt_(false),
     telemetryCallback(nullptr),
     handlingSegFault(false),
     handlingJitInterrupt_(false),
     interruptCallback(nullptr),
+    enqueuePromiseJobCallback(nullptr),
+    enqueuePromiseJobCallbackData(nullptr),
 #ifdef DEBUG
     exclusiveAccessOwner(nullptr),
     mainThreadHasExclusiveAccess(false),
 #endif
     numExclusiveThreads(0),
     numCompartments(0),
     localeCallbacks(nullptr),
     defaultLocale(nullptr),
@@ -756,16 +758,26 @@ FreeOp::~FreeOp()
 {
     for (size_t i = 0; i < freeLaterList.length(); i++)
         free_(freeLaterList[i]);
 
     if (!jitPoisonRanges.empty())
         jit::ExecutableAllocator::poisonCode(runtime(), jitPoisonRanges);
 }
 
+bool
+JSRuntime::enqueuePromiseJob(JSContext* cx, HandleFunction job)
+{
+    MOZ_ASSERT(cx->runtime()->enqueuePromiseJobCallback,
+               "Must set a callback using JS_SetEnqeueuPromiseJobCallback before using Promises");
+
+    void* data = cx->runtime()->enqueuePromiseJobCallbackData;
+    return cx->runtime()->enqueuePromiseJobCallback(cx, job, data);
+}
+
 void
 JSRuntime::updateMallocCounter(size_t nbytes)
 {
     updateMallocCounter(nullptr, nbytes);
 }
 
 void
 JSRuntime::updateMallocCounter(JS::Zone* zone, size_t nbytes)
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -899,16 +899,19 @@ struct JSRuntime : public JS::shadow::Ru
         handlingJitInterrupt_ = false;
     }
     bool handlingJitInterrupt() const {
         return handlingJitInterrupt_;
     }
 
     JSInterruptCallback interruptCallback;
 
+    JSEnqueuePromiseJobCallback enqueuePromiseJobCallback;
+    void* enqueuePromiseJobCallbackData;
+
 #ifdef DEBUG
     void assertCanLock(js::RuntimeLock which);
 #else
     void assertCanLock(js::RuntimeLock which) {}
 #endif
 
   private:
     /*
@@ -1007,16 +1010,18 @@ struct JSRuntime : public JS::shadow::Ru
     }
     bool hasJitRuntime() const {
         return !!jitRuntime_;
     }
     js::InterpreterStack& interpreterStack() {
         return interpreterStack_;
     }
 
+    bool enqueuePromiseJob(JSContext* cx, js::HandleFunction job);
+
     //-------------------------------------------------------------------------
     // Self-hosting support
     //-------------------------------------------------------------------------
 
     bool initSelfHosting(JSContext* cx);
     void finishSelfHosting();
     void markSelfHostingGlobal(JSTracer* trc);
     bool isSelfHostingGlobal(JSObject* global) {
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -20,16 +20,17 @@
 #include "jsweakmap.h"
 #include "jswrapper.h"
 #include "selfhosted.out.h"
 
 #include "builtin/Intl.h"
 #include "builtin/MapObject.h"
 #include "builtin/ModuleObject.h"
 #include "builtin/Object.h"
+#include "builtin/Promise.h"
 #include "builtin/Reflect.h"
 #include "builtin/SelfHostingDefines.h"
 #include "builtin/SIMD.h"
 #include "builtin/TypedObject.h"
 #include "builtin/WeakSetObject.h"
 #include "gc/Marking.h"
 #include "gc/Policy.h"
 #include "jit/AtomicOperations.h"
@@ -1488,16 +1489,37 @@ CallSelfHostedNonGenericMethod(JSContext
 
     if (!Invoke(cx, args2))
         return false;
 
     args.rval().set(args2.rval());
     return true;
 }
 
+bool
+js::CallSelfHostedFunction(JSContext* cx, const char* name, InvokeArgs& args)
+{
+    RootedAtom funAtom(cx, Atomize(cx, name, strlen(name)));
+    if (!funAtom)
+        return false;
+    RootedPropertyName funName(cx, funAtom->asPropertyName());
+    return CallSelfHostedFunction(cx, funName, args);
+}
+
+bool
+js::CallSelfHostedFunction(JSContext* cx, HandlePropertyName name, InvokeArgs& args)
+{
+    RootedValue fun(cx);
+    if (!GlobalObject::getIntrinsicValue(cx, cx->global(), name, &fun))
+        return false;
+    MOZ_ASSERT(fun.toObject().is<JSFunction>());
+    args.setCallee(fun);
+    return Invoke(cx, args);
+}
+
 template<typename T>
 bool
 Is(HandleValue v)
 {
     return v.isObject() && v.toObject().is<T>();
 }
 
 template<IsAcceptableThis Test>
@@ -1545,16 +1567,31 @@ js::ReportIncompatibleSelfHostedMethod(J
         }
         ++iter;
     }
 
     MOZ_ASSERT_UNREACHABLE("How did we not find a useful self-hosted frame?");
     return false;
 }
 
+// ES6, 25.4.1.6.
+static bool
+intrinsic_EnqueuePromiseJob(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+    MOZ_ASSERT(args[0].isObject());
+    MOZ_ASSERT(args[0].toObject().is<JSFunction>());
+
+    RootedFunction job(cx, &args[0].toObject().as<JSFunction>());
+    if (!cx->runtime()->enqueuePromiseJob(cx, job))
+        return false;
+    args.rval().setUndefined();
+    return true;
+}
 
 /**
  * Returns the default locale as a well-formed, but not necessarily canonicalized,
  * BCP-47 language tag.
  */
 static bool
 intrinsic_RuntimeDefaultLocale(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1665,16 +1702,43 @@ intrinsic_ConstructorForTypedArray(JSCon
     if (!GetBuiltinConstructor(cx, protoKey, &ctor))
         return false;
 
     args.rval().setObject(*ctor);
     return true;
 }
 
 static bool
+intrinsic_OriginalPromiseConstructor(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 0);
+
+    JSObject* obj = GlobalObject::getOrCreatePromiseConstructor(cx, cx->global());
+    if (!obj)
+        return false;
+
+    args.rval().setObject(*obj);
+    return true;
+}
+
+static bool
+intrinsic_IsWrappedPromiseObject(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+
+    RootedObject obj(cx, &args[0].toObject());
+    MOZ_ASSERT(!obj->is<PromiseObject>(),
+               "Unwrapped promises should be filtered out in inlineable code");
+    args.rval().setBoolean(JS::IsPromiseObject(obj));
+    return true;
+}
+
+static bool
 intrinsic_HostResolveImportedModule(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
     MOZ_ASSERT(args[0].toObject().is<ModuleObject>());
     MOZ_ASSERT(args[1].isString());
 
     RootedFunction moduleResolveHook(cx, cx->global()->moduleResolveHook());
@@ -2057,16 +2121,23 @@ static const JSFunctionSpec intrinsic_fu
           CallNonGenericSelfhostedMethod<Is<LegacyGeneratorObject>>, 2, 0),
     JS_FN("CallStarGeneratorMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<StarGeneratorObject>>, 2, 0),
 
     JS_FN("IsWeakSet", intrinsic_IsInstanceOfBuiltin<WeakSetObject>, 1,0),
     JS_FN("CallWeakSetMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
 
+    JS_FN("IsPromise",                      intrinsic_IsInstanceOfBuiltin<PromiseObject>, 1,0),
+    JS_FN("IsWrappedPromise",               intrinsic_IsWrappedPromiseObject,     1, 0),
+    JS_FN("_EnqueuePromiseJob",             intrinsic_EnqueuePromiseJob,          1, 0),
+    JS_FN("_GetOriginalPromiseConstructor", intrinsic_OriginalPromiseConstructor, 0, 0),
+    JS_FN("CallPromiseMethodIfWrapped",
+          CallNonGenericSelfhostedMethod<Is<PromiseObject>>,      2,0),
+
     // See builtin/TypedObject.h for descriptors of the typedobj functions.
     JS_FN("NewOpaqueTypedObject",           js::NewOpaqueTypedObject, 1, 0),
     JS_FN("NewDerivedTypedObject",          js::NewDerivedTypedObject, 3, 0),
     JS_FN("TypedObjectBuffer",              TypedObject::GetBuffer, 1, 0),
     JS_FN("TypedObjectByteOffset",          TypedObject::GetByteOffset, 1, 0),
     JS_FN("AttachTypedObject",              js::AttachTypedObject, 3, 0),
     JS_FN("TypedObjectIsAttached",          js::TypedObjectIsAttached, 1, 0),
     JS_FN("TypedObjectTypeDescr",           js::TypedObjectTypeDescr, 1, 0),
--- a/js/src/vm/SelfHosting.h
+++ b/js/src/vm/SelfHosting.h
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_SelfHosting_h_
 #define vm_SelfHosting_h_
 
 #include "jsapi.h"
 #include "NamespaceImports.h"
 
+#include "vm/Stack.h"
+
 class JSAtom;
 
 namespace js {
 
 /*
  * Check whether the given JSFunction is a self-hosted function whose
  * self-hosted name is the given name.
  */
@@ -26,11 +28,17 @@ IsCallSelfHostedNonGenericMethod(NativeI
 
 bool
 ReportIncompatibleSelfHostedMethod(JSContext* cx, const CallArgs& args);
 
 /* Get the compile options used when compiling self hosted code. */
 void
 FillSelfHostingCompileOptions(JS::CompileOptions& options);
 
+bool
+CallSelfHostedFunction(JSContext* cx, char const* name, InvokeArgs& args);
+
+bool
+CallSelfHostedFunction(JSContext* cx, HandlePropertyName name, InvokeArgs& args);
+
 } /* namespace js */
 
 #endif /* vm_SelfHosting_h_ */
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -79,16 +79,17 @@ IsJSXraySupported(JSProtoKey key)
     switch (key) {
       case JSProto_Date:
       case JSProto_Object:
       case JSProto_Array:
       case JSProto_Function:
       case JSProto_TypedArray:
       case JSProto_SavedFrame:
       case JSProto_RegExp:
+      case JSProto_Promise:
         return true;
       default:
         return false;
     }
 }
 
 XrayType
 GetXrayType(JSObject* obj)
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -928,17 +928,20 @@ public:
   virtual ~PromiseJobRunnable()
   {
   }
 
 protected:
   NS_IMETHOD
   Run() override
   {
-    mCallback->Call("promise callback");
+    nsIGlobalObject* global = xpc::NativeGlobal(mCallback->CallbackPreserveColor());
+    if (global && !global->IsDying()) {
+      mCallback->Call("promise callback");
+    }
     return NS_OK;
   }
 
 private:
   RefPtr<PromiseJobCallback> mCallback;
 };
 
 /* static */