author | Till Schneidereit <till@tillschneidereit.net> |
Tue, 10 Nov 2015 12:34:00 +0100 | |
changeset 289859 | 021f70a04fadc6155030df3d30d8c4f01278dd6a |
parent 289858 | 17385ac2980201df48efb904afa7da8af547b251 |
child 289860 | cf7722889ed96e7deaaaa9eef4b8b0caf8421d7d |
push id | 74020 |
push user | tschneidereit@gmail.com |
push date | Tue, 22 Mar 2016 23:02:59 +0000 |
treeherder | mozilla-inbound@e947c9941fe1 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | efaust |
bugs | 911216 |
milestone | 48.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
|
--- 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 --- /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");
--- 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 */