Backed out 2 changesets (bug 1313049) for various promise-related failures a=backout CLOSED TREE

Backed out changeset 8c08e1aca9ea (bug 1313049)
Backed out changeset 72764ba31b81 (bug 1313049)
This commit is contained in:
Wes Kocher 2016-10-28 16:15:11 -07:00
Родитель 78f5a63960
Коммит b3a0b0ef15
12 изменённых файлов: 2106 добавлений и 2342 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -12,31 +12,12 @@
namespace js {
enum PromiseSlots {
PromiseSlot_Flags = 0,
PromiseSlot_ReactionsOrResult,
PromiseSlot_RejectFunction,
PromiseSlot_AllocationSite,
PromiseSlot_ResolutionSite,
PromiseSlot_AllocationTime,
PromiseSlot_ResolutionTime,
PromiseSlot_Id,
PromiseSlots,
};
#define PROMISE_FLAG_RESOLVED 0x1
#define PROMISE_FLAG_FULFILLED 0x2
#define PROMISE_FLAG_HANDLED 0x4
#define PROMISE_FLAG_REPORTED 0x8
#define PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION 0x10
#define PROMISE_FLAG_DEFAULT_REJECT_FUNCTION 0x20
class AutoSetNewObjectMetadata;
class PromiseObject : public NativeObject
{
public:
static const unsigned RESERVED_SLOTS = PromiseSlots;
static const unsigned RESERVED_SLOTS = 8;
static const Class class_;
static const Class protoClass_;
static PromiseObject* create(JSContext* cx, HandleObject executor,
@ -46,7 +27,7 @@ class PromiseObject : public NativeObject
static JSObject* unforgeableReject(JSContext* cx, HandleValue value);
JS::PromiseState state() {
int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32();
int32_t flags = getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
if (!(flags & PROMISE_FLAG_RESOLVED)) {
MOZ_ASSERT(!(flags & PROMISE_FLAG_FULFILLED));
return JS::PromiseState::Pending;
@ -57,11 +38,11 @@ class PromiseObject : public NativeObject
}
Value value() {
MOZ_ASSERT(state() == JS::PromiseState::Fulfilled);
return getFixedSlot(PromiseSlot_ReactionsOrResult);
return getFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT);
}
Value reason() {
MOZ_ASSERT(state() == JS::PromiseState::Rejected);
return getFixedSlot(PromiseSlot_ReactionsOrResult);
return getFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT);
}
MOZ_MUST_USE bool resolve(JSContext* cx, HandleValue resolutionValue);
@ -69,13 +50,13 @@ class PromiseObject : public NativeObject
void onSettled(JSContext* cx);
double allocationTime() { return getFixedSlot(PromiseSlot_AllocationTime).toNumber(); }
double resolutionTime() { return getFixedSlot(PromiseSlot_ResolutionTime).toNumber(); }
double allocationTime() { return getFixedSlot(PROMISE_ALLOCATION_TIME_SLOT).toNumber(); }
double resolutionTime() { return getFixedSlot(PROMISE_RESOLUTION_TIME_SLOT).toNumber(); }
JSObject* allocationSite() {
return getFixedSlot(PromiseSlot_AllocationSite).toObjectOrNull();
return getFixedSlot(PROMISE_ALLOCATION_SITE_SLOT).toObjectOrNull();
}
JSObject* resolutionSite() {
return getFixedSlot(PromiseSlot_ResolutionSite).toObjectOrNull();
return getFixedSlot(PROMISE_RESOLUTION_SITE_SLOT).toObjectOrNull();
}
double lifetime();
double timeToResolution() {
@ -86,36 +67,45 @@ class PromiseObject : public NativeObject
uint64_t getID();
bool isUnhandled() {
MOZ_ASSERT(state() == JS::PromiseState::Rejected);
return !(getFixedSlot(PromiseSlot_Flags).toInt32() & PROMISE_FLAG_HANDLED);
return !(getFixedSlot(PROMISE_FLAGS_SLOT).toInt32() & PROMISE_FLAG_HANDLED);
}
void markAsReported() {
MOZ_ASSERT(isUnhandled());
int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32();
setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_REPORTED));
int32_t flags = getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
setFixedSlot(PROMISE_FLAGS_SLOT, Int32Value(flags | PROMISE_FLAG_REPORTED));
}
};
/**
* 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 `promise` field
* that can contain null. That field is only ever used by devtools, which have
* to treat these reactions specially.
* Tells the embedding to enqueue a Promise reaction job, based on six
* parameters:
* reaction handler - The callback to invoke for this job.
argument - The first and only argument to pass to the handler.
resolve - The Promise cabability's resolve hook, called upon normal
completion of the handler.
reject - The Promise cabability's reject hook, called if the handler
throws.
promise - The associated Promise, or null for some internal uses.
objectFromIncumbentGlobal - An object from the global that was the
incumbent global when the Promise reaction job
was created (not enqueued). Not the global
itself because unwrapping that might unwrap an
inner to an outer window, which we never want
to happen.
*/
MOZ_MUST_USE bool
EnqueuePromiseReactions(JSContext* cx, Handle<PromiseObject*> promise,
HandleObject dependentPromise,
HandleValue onFulfilled, HandleValue onRejected);
bool EnqueuePromiseReactionJob(JSContext* cx, HandleValue handler, HandleValue handlerArg,
HandleObject resolve, HandleObject reject,
HandleObject promise, HandleObject objectFromIncumbentGlobal);
MOZ_MUST_USE JSObject*
GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises);
MOZ_MUST_USE JSObject*
OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
HandleValue onRejected);
/**
* Tells the embedding to enqueue a Promise resolve thenable job, based on six
* parameters:
* promiseToResolve - The promise to resolve, obviously.
* thenable - The thenable to resolve the Promise with.
* then - The `then` function to invoke with the `thenable` as the receiver.
*/
bool EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve,
HandleValue thenable, HandleValue then);
/**
* A PromiseTask represents a task that can be dispatched to a helper thread

Просмотреть файл

@ -2,6 +2,503 @@
* 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};
function PromiseReactionRecord(promise, resolve, reject, fulfillHandler, rejectHandler,
incumbentGlobal) {
this.promise = promise;
this.resolve = resolve;
this.reject = reject;
this.fulfillHandler = fulfillHandler;
this.rejectHandler = rejectHandler;
this.incumbentGlobal = incumbentGlobal;
}
MakeConstructible(PromiseReactionRecord, PromiseReactionRecordProto);
// Used to verify that an object is a PromiseCapability record.
var PromiseCapabilityRecordProto = {__proto__: null};
// ES2016, 25.4.1.3, implemented in Promise.cpp.
// ES2016, 25.4.1.4, implemented in Promise.cpp.
// ES2016, 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
};
}
// ES2016, 25.4.1.6, implemented in SelfHosting.cpp.
// ES2016, 25.4.1.7, implemented in Promise.cpp.
// ES2016, 25.4.1.8, implemented in Promise.cpp.
// ES2016, 25.4.1.9, implemented in SelfHosting.cpp.
// ES6, 25.4.2.1.
function EnqueuePromiseReactionJob(reaction, jobType, argument) {
// Reaction records contain handlers for both fulfillment and rejection.
// The `jobType` parameter allows us to retrieves the right one.
assert(jobType === PROMISE_JOB_TYPE_FULFILL || jobType === PROMISE_JOB_TYPE_REJECT,
"Invalid job type");
_EnqueuePromiseReactionJob(reaction[jobType],
argument,
reaction.resolve,
reaction.reject,
reaction.promise,
reaction.incumbentGlobal || null);
}
// ES6, 25.4.2.2. (Implemented in C++).
// ES6, 25.4.3.1. (Implemented in C++).
// ES2016, 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;
let allPromise = resultCapability.promise;
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 allPromise;
}
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.
BlockOnPromise(nextPromise, allPromise, 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).
};
}
// ES2016, 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;
}
}
// ES2016, 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 racePromise = resultCapability.promise;
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 racePromise;
}
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.
BlockOnPromise(nextPromise, racePromise, resultCapability.resolve,
resultCapability.reject);
}
assert(false, "Shouldn't reach the end of PerformPromiseRace");
}
/**
* Calls |promise.then| with the provided hooks and adds |blockedPromise| to
* its list of dependent promises. Used by |Promise.all| and |Promise.race|.
*
* If |promise.then| is the original |Promise.prototype.then| function and
* the call to |promise.then| would use the original |Promise| constructor to
* create the resulting promise, this function skips the call to |promise.then|
* and thus creating a new promise that would not be observable by content.
*/
function BlockOnPromise(promise, blockedPromise, onResolve, onReject) {
let then = promise.then;
// By default, the blocked promise is added as an extra entry to the
// rejected promises list.
let addToDependent = true;
if (then === Promise_then && IsObject(promise) && IsPromise(promise)) {
// |then| is the original |Promise.prototype.then|, inline it here.
// 25.4.5.3., steps 3-4.
let PromiseCtor = GetBuiltinConstructor('Promise');
let C = SpeciesConstructor(promise, PromiseCtor);
let resultCapability;
if (C === PromiseCtor) {
resultCapability = {
__proto__: PromiseCapabilityRecordProto,
promise: blockedPromise,
reject: NullFunction,
resolve: NullFunction
};
addToDependent = false;
} else {
// 25.4.5.3., steps 5-6.
resultCapability = NewPromiseCapability(C);
}
// 25.4.5.3., step 7.
PerformPromiseThen(promise, onResolve, onReject, resultCapability);
} else {
// Optimization failed, do the normal call.
callContentFunction(then, promise, onResolve, onReject);
}
if (!addToDependent)
return;
// The object created by the |promise.then| call or the inlined version
// of it above is visible to content (either because |promise.then| was
// overridden by content and could leak it, or because a constructor
// other than the original value of |Promise| was used to create it).
// To have both that object and |blockedPromise| show up as dependent
// promises in the debugger, add a dummy reaction to the list of reject
// reactions that contains |blockedPromise|, but otherwise does nothing.
// If the object isn't a maybe-wrapped instance of |Promise|, we ignore
// it. All this does is lose some small amount of debug information
// in scenarios that are highly unlikely to occur in useful code.
if (IsPromise(promise))
return callFunction(AddDependentPromise, promise, blockedPromise);
if (IsWrappedPromise(promise))
callFunction(CallPromiseMethodIfWrapped, promise, blockedPromise, "AddDependentPromise");
}
/**
* Invoked with a Promise as the receiver, AddDependentPromise adds an entry
* to the reactions list.
*
* This is only used to make dependent promises visible in the devtools, so no
* callbacks are provided. To make handling that case easier elsewhere,
* they're all set to NullFunction.
*
* The reason for the target Promise to be passed as the receiver is so the
* same function can be used for wrapped and unwrapped Promise objects.
*/
function AddDependentPromise(dependentPromise) {
assert(IsPromise(this), "AddDependentPromise expects an unwrapped Promise as the receiver");
if (GetPromiseState(this) !== PROMISE_STATE_PENDING)
return;
let reaction = new PromiseReactionRecord(dependentPromise, NullFunction, NullFunction,
NullFunction, NullFunction, null);
let reactions = UnsafeGetReservedSlot(this, PROMISE_REACTIONS_OR_RESULT_SLOT);
// The reactions list might not have been allocated yet.
if (!reactions)
UnsafeSetReservedSlot(dependentPromise, PROMISE_REACTIONS_OR_RESULT_SLOT, [reaction]);
else
_DefineDataProperty(reactions, reactions.length, reaction);
}
// ES2016, 25.4.4.4 (implemented in C++).
// ES2016, 25.4.4.5 (implemented in C++).
// ES6, 25.4.4.6.
function Promise_static_get_species() {
// Step 1.
@ -14,3 +511,252 @@ 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) {
// See comment above GetPromiseHandlerForwarders for why this is needed.
let handlerForwarders = GetPromiseHandlerForwarders(onFulfilled, onRejected);
return callFunction(CallPromiseMethodIfWrapped, promise,
handlerForwarders[0], handlerForwarders[1],
resultCapability.promise, resultCapability.resolve,
resultCapability.reject, "UnwrappedPerformPromiseThen");
}
return PerformPromiseThen(promise, 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 `promise` field
* that 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) {
// See comment above GetPromiseHandlerForwarders for why this is needed.
let handlerForwarders = GetPromiseHandlerForwarders(onFulfilled, onRejected);
return callFunction(CallPromiseMethodIfWrapped, promise, handlerForwarders[0],
handlerForwarders[1], dependentPromise, NullFunction, NullFunction,
"UnwrappedPerformPromiseThen");
}
let capability = {
__proto__: PromiseCapabilityRecordProto,
promise: dependentPromise,
resolve: NullFunction,
reject: NullFunction
};
return PerformPromiseThen(promise, onFulfilled, onRejected, capability);
}
/**
* Returns a set of functions that are (1) self-hosted, and (2) exact
* forwarders of the passed-in functions, for use by
* UnwrappedPerformPromiseThen.
*
* When calling `then` on an xray-wrapped promise, the receiver isn't
* unwrapped. Instead, Promise_then operates on the wrapped Promise. Just
* calling PerformPromiseThen from Promise_then as we normally would doesn't
* work in this case: PerformPromiseThen can only deal with unwrapped
* Promises. Instead, we use the CallPromiseMethodIfWrapped intrinsic to
* switch compartments before calling PerformPromiseThen, via
* UnwrappedPerformPromiseThen.
*
* This is almost enough, but there's an additional wrinkle: when calling the
* fulfillment and rejection handlers, we might pass in Object-type arguments
* from within the xray-ed, lower-privileged compartment. By default, this
* doesn't work, because they're wrapped into wrappers that disallow passing
* in Object-typed arguments (so the higher-privileged code doesn't
* accidentally operate on objects assuming they're higher-privileged, too.)
* So instead UnwrappedPerformPromiseThen adds another level of indirection:
* it closes over the, by now cross-compartment-wrapped, handler forwarders
* created by GetPromiseHandlerForwarders and creates a second set of
* forwarders around them, which use UnsafeCallWrappedFunction to call the
* initial forwarders.
* Note that both above-mentioned guarantees are required: while it may seem
* as though the original handlers would always be wrappers once they reach
* UnwrappedPerformPromiseThen (because the call to `then` originated in the
* higher-privileged compartment, and after unwrapping we end up in the
* lower-privileged one), that's not necessarily the case. One or both of the
* handlers can originate from the lower-privileged compartment, so they'd
* actually be unwrapped functions when they reach
* UnwrappedPerformPromiseThen.
*/
function GetPromiseHandlerForwarders(fulfilledHandler, rejectedHandler) {
// If non-callable values are passed, we have to preserve them so steps
// 3 and 4 of PerformPromiseThen work as expected.
return [
IsCallable(fulfilledHandler) ? function onFulfilled(argument) {
return callContentFunction(fulfilledHandler, this, argument);
} : fulfilledHandler,
IsCallable(rejectedHandler) ? function onRejected(argument) {
return callContentFunction(rejectedHandler, this, argument);
} : rejectedHandler
];
}
/**
* Forwarder used to invoke PerformPromiseThen on an unwrapped Promise, while
* wrapping the resolve/reject callbacks into functions that invoke them in
* their original compartment. See the comment for GetPromiseHandlerForwarders
* for details.
*/
function UnwrappedPerformPromiseThen(fulfilledHandler, rejectedHandler, promise, resolve, reject) {
let resultCapability = {
__proto__: PromiseCapabilityRecordProto,
promise,
resolve(resolution) {
// Under some circumstances, we have an unwrapped `resolve`
// function here. One way this happens is if the constructor
// passed to `NewPromiseCapability` is from the same global as the
// Promise object on which `Promise_then` was called, but where
// `Promise_then` is from a different global, so we end up here.
// In that case, the `resolve` and `reject` functions aren't
// wrappers in the current global.
if (IsFunctionObject(resolve))
return resolve(resolution);
return UnsafeCallWrappedFunction(resolve, undefined, resolution);
},
reject(reason) {
// See comment inside `resolve` above.
if (IsFunctionObject(reject))
return reject(reason);
return UnsafeCallWrappedFunction(reject, undefined, reason);
}
};
function onFulfilled(argument) {
return UnsafeCallWrappedFunction(fulfilledHandler, undefined, argument);
}
function onRejected(argument) {
return UnsafeCallWrappedFunction(rejectedHandler, undefined, argument);
}
return PerformPromiseThen(this, IsCallable(fulfilledHandler) ? onFulfilled : fulfilledHandler,
IsCallable(rejectedHandler) ? onRejected : rejectedHandler,
resultCapability);
}
// ES2016, 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;
let incumbentGlobal = _GetObjectFromIncumbentGlobal();
// Steps 5,6.
// Instead of creating separate reaction records for fulfillment and
// rejection, we create a combined record. All places we use the record
// can handle that.
let reaction = new PromiseReactionRecord(resultCapability.promise,
resultCapability.resolve,
resultCapability.reject,
onFulfilled,
onRejected,
incumbentGlobal);
// Step 7.
let state = GetPromiseState(promise);
let flags = UnsafeGetInt32FromReservedSlot(promise, PROMISE_FLAGS_SLOT);
if (state === PROMISE_STATE_PENDING) {
// Steps 7.a,b.
// We only have a single list for fulfill and reject reactions.
let reactions = UnsafeGetReservedSlot(promise, PROMISE_REACTIONS_OR_RESULT_SLOT);
if (!reactions)
UnsafeSetReservedSlot(promise, PROMISE_REACTIONS_OR_RESULT_SLOT, [reaction]);
else
_DefineDataProperty(reactions, reactions.length, reaction);
}
// Step 8.
else if (state === PROMISE_STATE_FULFILLED) {
// Step 8.a.
let value = UnsafeGetReservedSlot(promise, PROMISE_REACTIONS_OR_RESULT_SLOT);
// Step 8.b.
EnqueuePromiseReactionJob(reaction, PROMISE_JOB_TYPE_FULFILL, value);
}
// Step 9.
else {
// Step 9.a.
assert(state === PROMISE_STATE_REJECTED, "Invalid Promise state " + state);
// Step 9.b.
let reason = UnsafeGetReservedSlot(promise, PROMISE_REACTIONS_OR_RESULT_SLOT);
// Step 9.c.
if (!(flags & PROMISE_FLAG_HANDLED))
HostPromiseRejectionTracker(promise, PROMISE_REJECTION_TRACKER_OPERATION_HANDLE);
// Step 9.d.
EnqueuePromiseReactionJob(reaction, PROMISE_JOB_TYPE_REJECT, reason);
}
// Step 10.
UnsafeSetReservedSlot(promise, PROMISE_FLAGS_SLOT, flags | PROMISE_FLAG_HANDLED);
// Step 11.
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) {
let flags = UnsafeGetInt32FromReservedSlot(promise, PROMISE_FLAGS_SLOT);
if (!(flags & PROMISE_FLAG_RESOLVED)) {
assert(!(flags & PROMISE_STATE_FULFILLED), "Fulfilled promises are resolved, too");
return PROMISE_STATE_PENDING;
}
if (flags & PROMISE_FLAG_FULFILLED)
return PROMISE_STATE_FULFILLED;
return PROMISE_STATE_REJECTED;
}

Просмотреть файл

@ -78,6 +78,33 @@
#define ITEM_KIND_VALUE 1
#define ITEM_KIND_KEY_AND_VALUE 2
#define PROMISE_FLAGS_SLOT 0
#define PROMISE_REACTIONS_OR_RESULT_SLOT 1
#define PROMISE_RESOLVE_FUNCTION_SLOT 2
#define PROMISE_ALLOCATION_SITE_SLOT 3
#define PROMISE_RESOLUTION_SITE_SLOT 4
#define PROMISE_ALLOCATION_TIME_SLOT 5
#define PROMISE_RESOLUTION_TIME_SLOT 6
#define PROMISE_ID_SLOT 7
#define PROMISE_STATE_PENDING 0
#define PROMISE_STATE_FULFILLED 1
#define PROMISE_STATE_REJECTED 2
#define PROMISE_FLAG_RESOLVED 0x1
#define PROMISE_FLAG_FULFILLED 0x2
#define PROMISE_FLAG_HANDLED 0x4
#define PROMISE_FLAG_REPORTED 0x8
#define PROMISE_HANDLER_IDENTITY 0
#define PROMISE_HANDLER_THROWER 1
#define PROMISE_REJECTION_TRACKER_OPERATION_REJECT false
#define PROMISE_REJECTION_TRACKER_OPERATION_HANDLE true
#define PROMISE_JOB_TYPE_FULFILL "fulfillHandler"
#define PROMISE_JOB_TYPE_REJECT "rejectHandler"
// 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 */

Просмотреть файл

@ -1451,42 +1451,15 @@ SettlePromiseNow(JSContext* cx, unsigned argc, Value* vp)
}
RootedNativeObject promise(cx, &args[0].toObject().as<NativeObject>());
int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
promise->setFixedSlot(PromiseSlot_Flags,
int32_t flags = promise->getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
promise->setFixedSlot(PROMISE_FLAGS_SLOT,
Int32Value(flags | PROMISE_FLAG_RESOLVED | PROMISE_FLAG_FULFILLED));
promise->setFixedSlot(PromiseSlot_ReactionsOrResult, UndefinedValue());
promise->setFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT, UndefinedValue());
JS::dbg::onPromiseSettled(cx, promise);
return true;
}
static bool
GetWaitForAllPromise(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "getWaitForAllPromise", 1))
return false;
if (!args[0].isObject() || !IsPackedArray(&args[0].toObject())) {
JS_ReportErrorASCII(cx, "first argument must be a dense Array of Promise objects");
return false;
}
RootedNativeObject list(cx, &args[0].toObject().as<NativeObject>());
AutoObjectVector promises(cx);
uint32_t count = list->getDenseInitializedLength();
if (!promises.resize(count))
return false;
for (uint32_t i = 0; i < count; i++)
promises[i].set(&list->getDenseElement(i).toObject());
RootedObject resultPromise(cx, JS::GetWaitForAllPromise(cx, promises));
if (!resultPromise)
return false;
args.rval().set(ObjectValue(*resultPromise));
return true;
}
#else
static const js::Class FakePromiseClass = {
@ -4138,10 +4111,6 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = {
" with a value of `undefined` and causes the firing of any onPromiseSettled\n"
" hooks set on Debugger instances that are observing the given promise's\n"
" global as a debuggee."),
JS_FN_HELP("getWaitForAllPromise", GetWaitForAllPromise, 1, 0,
"getWaitForAllPromise(densePromisesArray)",
" Calls the 'GetWaitForAllPromise' JSAPI function and returns the result\n"
" Promise."),
#else
JS_FN_HELP("makeFakePromise", MakeFakePromise, 0, 0,
"makeFakePromise()",

Просмотреть файл

@ -4792,56 +4792,97 @@ JS::RejectPromise(JSContext* cx, JS::HandleObject promise, JS::HandleValue rejec
}
JS_PUBLIC_API(JSObject*)
JS::CallOriginalPromiseThen(JSContext* cx, JS::HandleObject promiseObj,
JS::HandleObject onResolveObj, JS::HandleObject onRejectObj)
JS::CallOriginalPromiseThen(JSContext* cx, JS::HandleObject promise,
JS::HandleObject onResolve, JS::HandleObject onReject)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, promiseObj, onResolveObj, onRejectObj);
assertSameCompartment(cx, promise, onResolve, onReject);
MOZ_ASSERT_IF(onResolveObj, IsCallable(onResolveObj));
MOZ_ASSERT_IF(onRejectObj, IsCallable(onRejectObj));
MOZ_ASSERT(promise->is<PromiseObject>());
MOZ_ASSERT(onResolve == nullptr || IsCallable(onResolve));
MOZ_ASSERT(onReject == nullptr || IsCallable(onReject));
Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
RootedValue onFulfilled(cx, ObjectOrNullValue(onResolveObj));
RootedValue onRejected(cx, ObjectOrNullValue(onRejectObj));
return OriginalPromiseThen(cx, promise, onFulfilled, onRejected);
JSObject* obj;
{
FixedInvokeArgs<2> args(cx);
args[0].setObjectOrNull(onResolve);
args[1].setObjectOrNull(onReject);
RootedValue thisvOrRval(cx, ObjectValue(*promise));
if (!CallSelfHostedFunction(cx, "Promise_then", thisvOrRval, args, &thisvOrRval))
return nullptr;
MOZ_ASSERT(thisvOrRval.isObject());
obj = &thisvOrRval.toObject();
}
MOZ_ASSERT(obj->is<PromiseObject>());
return obj;
}
JS_PUBLIC_API(bool)
JS::AddPromiseReactions(JSContext* cx, JS::HandleObject promiseObj,
JS::HandleObject onResolvedObj, JS::HandleObject onRejectedObj)
JS::AddPromiseReactions(JSContext* cx, JS::HandleObject promise,
JS::HandleObject onResolve, JS::HandleObject onReject)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, promiseObj, onResolvedObj, onRejectedObj);
assertSameCompartment(cx, promise, onResolve, onReject);
MOZ_ASSERT(IsCallable(onResolvedObj));
MOZ_ASSERT(IsCallable(onRejectedObj));
MOZ_ASSERT(promise->is<PromiseObject>());
MOZ_ASSERT(IsCallable(onResolve));
MOZ_ASSERT(IsCallable(onReject));
Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
RootedValue onResolved(cx, ObjectValue(*onResolvedObj));
RootedValue onRejected(cx, ObjectValue(*onRejectedObj));
return EnqueuePromiseReactions(cx, promise, nullptr, onResolved, onRejected);
FixedInvokeArgs<4> args(cx);
args[0].setObject(*promise);
args[1].setNull();
args[2].setObject(*onResolve);
args[3].setObject(*onReject);
RootedValue dummy(cx);
return CallSelfHostedFunction(cx, "EnqueuePromiseReactions", UndefinedHandleValue, args,
&dummy);
}
/**
* 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 that the array is dense and all entries are Promise objects.
*/
JS_PUBLIC_API(JSObject*)
JS::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
return js::GetWaitForAllPromise(cx, 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];
assertSameCompartment(cx, obj);
if (IsWrapper(obj))
obj = UncheckedUnwrap(obj);
MOZ_ASSERT(obj->is<PromiseObject>());
#endif
arr->setDenseElement(i, ObjectValue(*promises[i]));
}
JSObject* obj;
{
FixedInvokeArgs<1> args(cx);
args[0].setObject(*arr);
RootedValue thisvOrRval(cx, UndefinedValue());
if (!CallSelfHostedFunction(cx, "GetWaitForAllPromise", thisvOrRval, args, &thisvOrRval))
return nullptr;
MOZ_ASSERT(thisvOrRval.isObject());
obj = &thisvOrRval.toObject();
}
MOZ_ASSERT(obj->is<PromiseObject>());
return obj;
}
JS_PUBLIC_API(void)

Просмотреть файл

@ -1974,47 +1974,6 @@ js::IsStandardPrototype(JSObject* obj, JSProtoKey key)
return v.isObject() && obj == &v.toObject();
}
/**
* Returns the original Object.prototype from the embedding-provided incumbent
* global.
*
* Really, we want the incumbent global itself so we can pass it to other
* embedding hooks which need it. Specifically, the enqueue promise hook
* takes an incumbent global so it can set that on the PromiseCallbackJob
* it creates.
*
* The reason for not just returning the global itself is that we'd need to
* wrap it into the current compartment, and later unwrap it. Unwrapping
* globals is tricky, though: we might accidentally unwrap through an inner
* to its outer window and end up with the wrong global. Plain objects don't
* have this problem, so we use the global's Object.prototype. The code using
* it - e.g. EnqueuePromiseReactionJob - can then unwrap the object and get
* its global without fear of unwrapping too far.
*/
bool
js::GetObjectFromIncumbentGlobal(JSContext* cx, MutableHandleObject obj)
{
RootedObject globalObj(cx, cx->runtime()->getIncumbentGlobal(cx));
if (!globalObj) {
obj.set(nullptr);
return true;
}
{
AutoCompartment ac(cx, globalObj);
obj.set(globalObj->as<GlobalObject>().getOrCreateObjectPrototype(cx));
if (!obj)
return false;
}
// The object might be from a different compartment, so wrap it.
if (obj && !cx->compartment()->wrap(cx, obj))
return false;
return true;
}
JSProtoKey
JS::IdentifyStandardInstance(JSObject* obj)
{
@ -3943,16 +3902,6 @@ js::SpeciesConstructor(JSContext* cx, HandleObject obj, HandleValue defaultCtor,
return true;
}
bool
js::SpeciesConstructor(JSContext* cx, HandleObject obj, JSProtoKey ctorKey,
MutableHandleValue pctor)
{
if (!GlobalObject::ensureConstructor(cx, cx->global(), ctorKey))
return false;
RootedValue defaultCtor(cx, cx->global()->getConstructor(ctorKey));
return SpeciesConstructor(cx, obj, defaultCtor, pctor);
}
bool
js::Unbox(JSContext* cx, HandleObject obj, MutableHandleValue vp)
{

Просмотреть файл

@ -1375,12 +1375,6 @@ TestIntegrityLevel(JSContext* cx, HandleObject obj, IntegrityLevel level, bool*
extern bool
SpeciesConstructor(JSContext* cx, HandleObject obj, HandleValue defaultCtor, MutableHandleValue pctor);
extern bool
SpeciesConstructor(JSContext* cx, HandleObject obj, JSProtoKey ctorKey, MutableHandleValue pctor);
extern bool
GetObjectFromIncumbentGlobal(JSContext* cx, MutableHandleObject obj);
} /* namespace js */
#endif /* jsobj_h */

Просмотреть файл

@ -5,6 +5,8 @@ if (!this.Promise) {
quit(0);
}
let GetWaitForAllPromise = getSelfHostedValue('GetWaitForAllPromise');
function onResolved(val) {
result = 'resolved with ' + val;
}
@ -13,18 +15,18 @@ function onRejected(val) {
result = 'rejected with ' + val;
}
// Replacing `Promise#then` shouldn't affect getWaitForAllPromise.
// Replacing `Promise#then` shouldn't affect GetWaitForAllPromise.
let originalThen = Promise.prototype.then;
Promise.prototype.then = 1;
// Replacing Promise[@@species] shouldn't affect getWaitForAllPromise.
// Replacing Promise[@@species] shouldn't affect GetWaitForAllPromise.
Promise[Symbol.species] = function(){};
// Replacing `Promise` shouldn't affect getWaitForAllPromise.
// Replacing `Promise` shouldn't affect GetWaitForAllPromise.
let PromiseCtor = Promise;
Promise = {};
// Replacing Array[@@iterator] shouldn't affect getWaitForAllPromise.
// Replacing Array[@@iterator] shouldn't affect GetWaitForAllPromise.
Array.prototype[Symbol.iterator] = function(){};
let resolveFunctions = [];
@ -38,7 +40,7 @@ for (let i = 0; i < 3; i++) {
promises.push(p);
}
let allPromise = getWaitForAllPromise(promises);
let allPromise = GetWaitForAllPromise(promises);
let then = originalThen.call(allPromise, onResolved, onRejected);
resolveFunctions.forEach((fun, i)=>fun(i));
@ -48,14 +50,14 @@ 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));
originalThen.call(GetWaitForAllPromise([]), v=>(result = v));
drainJobQueue();
assertEq(result instanceof Array, true);
assertEq(result.length, 0);
//Empty lists result in a promise resolved with an empty array.
result = undefined;
originalThen.call(getWaitForAllPromise([]), v=>(result = v));
originalThen.call(GetWaitForAllPromise([]), v=>(result = v));
drainJobQueue();

Просмотреть файл

@ -121,6 +121,7 @@
macro(frame, frame, "frame") \
macro(from, from, "from") \
macro(fulfilled, fulfilled, "fulfilled") \
macro(fulfillHandler, fulfillHandler, "fulfillHandler") \
macro(futexNotEqual, futexNotEqual, "not-equal") \
macro(futexOK, futexOK, "ok") \
macro(futexTimedOut, futexTimedOut, "timed-out") \
@ -253,6 +254,7 @@
macro(Reify, Reify, "Reify") \
macro(reject, reject, "reject") \
macro(rejected, rejected, "rejected") \
macro(rejectHandler, rejectHandler, "rejectHandler") \
macro(RequireObjectCoercible, RequireObjectCoercible, "RequireObjectCoercible") \
macro(resolve, resolve, "resolve") \
macro(resumeGenerator, resumeGenerator, "resumeGenerator") \

Просмотреть файл

@ -733,8 +733,7 @@ JSRuntime::enqueuePromiseJob(JSContext* cx, HandleFunction job, HandleObject pro
// intrinsic_EnqueuePromiseReactionJob for details.
if (IsWrapper(promise))
unwrappedPromise = UncheckedUnwrap(promise);
if (unwrappedPromise->is<PromiseObject>())
allocationSite = JS::GetPromiseAllocationSite(unwrappedPromise);
allocationSite = JS::GetPromiseAllocationSite(unwrappedPromise);
}
return cx->runtime()->enqueuePromiseJobCallback(cx, job, allocationSite, incumbentGlobal, data);
}

Просмотреть файл

@ -27,6 +27,7 @@
#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"
@ -177,6 +178,59 @@ intrinsic_IsConstructor(JSContext* cx, unsigned argc, Value* vp)
return true;
}
/**
* Intrinsic for calling a wrapped self-hosted function without invoking the
* wrapper's security checks.
*
* Takes a wrapped function as the first and the receiver object as the
* second argument. Any additional arguments are passed on to the unwrapped
* function.
*
* Xray wrappers prevent lower-privileged code from passing objects to wrapped
* functions from higher-privileged realms. In some cases, this check is too
* strict, so this intrinsic allows getting around it.
*
* Note that it's not possible to replace all usages with dedicated intrinsics
* as the function in question might be an inner function that closes over
* state relevant to its execution.
*
* Right now, this is used for the Promise implementation to enable creating
* resolution functions for xrayed Promises in the privileged realm and then
* creating the Promise instance in the non-privileged one. The callbacks have
* to be called by non-privileged code in various places, in many cases
* passing objects as arguments.
*/
static bool
intrinsic_UnsafeCallWrappedFunction(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() >= 2);
MOZ_ASSERT(IsCallable(args[0]));
MOZ_ASSERT(IsWrapper(&args[0].toObject()));
MOZ_ASSERT(args[1].isObject() || args[1].isUndefined());
MOZ_RELEASE_ASSERT(args[0].isObject());
RootedObject wrappedFun(cx, &args[0].toObject());
RootedObject fun(cx, UncheckedUnwrap(wrappedFun));
MOZ_RELEASE_ASSERT(fun->is<JSFunction>());
MOZ_RELEASE_ASSERT(fun->as<JSFunction>().isSelfHostedOrIntrinsic());
InvokeArgs args2(cx);
if (!args2.init(cx, args.length() - 2))
return false;
args2.setThis(args[1]);
for (size_t i = 0; i < args2.length(); i++)
args2[i].set(args[i + 2]);
AutoWaivePolicy waivePolicy(cx, wrappedFun, JSID_VOIDHANDLE, BaseProxyHandler::CALL);
if (!CrossCompartmentWrapper::singleton.call(cx, wrappedFun, args2))
return false;
args.rval().set(args2.rval());
return true;
}
template<typename T>
static bool
intrinsic_IsInstanceOfBuiltin(JSContext* cx, unsigned argc, Value* vp)
@ -1831,6 +1885,54 @@ js::ReportIncompatibleSelfHostedMethod(JSContext* cx, const CallArgs& args)
return false;
}
static bool
intrinsic_EnqueuePromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 6);
RootedValue handler(cx, args[0]);
MOZ_ASSERT((handler.isNumber() &&
(handler.toNumber() == PROMISE_HANDLER_IDENTITY ||
handler.toNumber() == PROMISE_HANDLER_THROWER)) ||
handler.toObject().isCallable());
RootedValue handlerArg(cx, args[1]);
RootedObject resolve(cx, &args[2].toObject());
MOZ_ASSERT(IsCallable(resolve));
RootedObject reject(cx, &args[3].toObject());
MOZ_ASSERT(IsCallable(reject));
RootedObject promise(cx, args[4].toObjectOrNull());
RootedObject objectFromIncumbentGlobal(cx, args[5].toObjectOrNull());
if (!EnqueuePromiseReactionJob(cx, handler, handlerArg, resolve, reject, promise,
objectFromIncumbentGlobal))
{
return false;
}
args.rval().setUndefined();
return true;
}
// ES2016, February 12 draft, 25.4.1.9.
static bool
intrinsic_HostPromiseRejectionTracker(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].toObject().is<PromiseObject>());
Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>());
mozilla::DebugOnly<bool> isHandled = args[1].toBoolean();
MOZ_ASSERT(isHandled, "HostPromiseRejectionTracker intrinsic currently only marks as handled");
cx->runtime()->removeUnhandledRejectedPromise(cx, promise);
args.rval().setUndefined();
return true;
}
/**
* Returns the default locale as a well-formed, but not necessarily canonicalized,
* BCP-47 language tag.
@ -1957,6 +2059,61 @@ intrinsic_NameForTypedArray(JSContext* cx, unsigned argc, Value* vp)
return true;
}
/**
* Returns an object created in the embedding-provided incumbent global.
*
* Really, we want the incumbent global itself so we can pass it to other
* embedding hooks which need it. Specifically, the enqueue promise hook
* takes an incumbent global so it can set that on the PromiseCallbackJob
* it creates.
*
* The reason for not just returning the global itself is that we'd need to
* wrap it into the current compartment, and later unwrap it. Unwrapping
* globals is tricky, though: we might accidentally unwrap through an inner
* to its outer window and end up with the wrong global. Plain objects don't
* have this problem, so we create one and return it. The code using it -
* e.g. EnqueuePromiseReactionJob - can then unwrap the object and get its
* global without fear of unwrapping too far.
*/
static bool
intrinsic_GetObjectFromIncumbentGlobal(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedObject obj(cx);
RootedObject global(cx, cx->runtime()->getIncumbentGlobal(cx));
if (global) {
MOZ_ASSERT(global->is<GlobalObject>());
AutoCompartment ac(cx, global);
obj = NewBuiltinClassInstance<PlainObject>(cx);
if (!obj)
return false;
}
RootedValue objVal(cx, ObjectOrNullValue(obj));
// The object might be from a different compartment, so wrap it.
if (obj && !cx->compartment()->wrap(cx, &objVal))
return false;
args.rval().set(objVal);
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(CheckedUnwrap(obj)->is<PromiseObject>());
return true;
}
static bool
intrinsic_HostResolveImportedModule(JSContext* cx, unsigned argc, Value* vp)
{
@ -2251,6 +2408,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_INLINABLE_FN("UnsafeGetBooleanFromReservedSlot", intrinsic_UnsafeGetBooleanFromReservedSlot,2,0,
IntrinsicUnsafeGetBooleanFromReservedSlot),
JS_FN("UnsafeCallWrappedFunction", intrinsic_UnsafeCallWrappedFunction,2,0),
JS_FN("NewArrayInCompartment", intrinsic_NewArrayInCompartment, 1,0),
JS_FN("IsPackedArray", intrinsic_IsPackedArray, 1,0),
@ -2363,6 +2521,14 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("CallWeakSetMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
JS_FN("_GetObjectFromIncumbentGlobal", intrinsic_GetObjectFromIncumbentGlobal, 0, 0),
JS_FN("IsPromise", intrinsic_IsInstanceOfBuiltin<PromiseObject>, 1,0),
JS_FN("IsWrappedPromise", intrinsic_IsWrappedPromiseObject, 1, 0),
JS_FN("_EnqueuePromiseReactionJob", intrinsic_EnqueuePromiseReactionJob, 2, 0),
JS_FN("HostPromiseRejectionTracker", intrinsic_HostPromiseRejectionTracker,2, 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),