Bug 911216 - Part 27: Properly set up incumbent and current globals for Promise reaction jobs. r=efaust,bz

This commit is contained in:
Till Schneidereit 2016-07-02 02:00:47 +02:00
parent 1eabe5f054
commit 14a62c9039
11 changed files with 467 additions and 92 deletions

View File

@ -443,20 +443,86 @@ PromiseObject::onSettled(JSContext* cx)
JS::dbg::onPromiseSettled(cx, promise);
}
enum ReactionJobSlots {
ReactionJobSlot_Handler = 0,
ReactionJobSlot_JobData,
};
enum ReactionJobDataSlots {
ReactionJobDataSlot_HandlerArg = 0,
ReactionJobDataSlot_ResolveHook,
ReactionJobDataSlot_RejectHook,
ReactionJobDataSlotsCount,
};
// ES6, 25.4.2.1.
bool
/**
* Callback triggering the fulfill/reject reaction for a resolved Promise,
* to be invoked by the embedding during its processing of the Promise job
* queue.
*
* See http://www.ecma-international.org/ecma-262/6.0/index.html#sec-jobs-and-job-queues
*
* A PromiseReactionJob is set as the native function of an extended
* JSFunction object, with all information required for the job's
* execution stored in the function's extended slots.
*
* Usage of the function's extended slots is as follows:
* ReactionJobSlot_Handler: The handler to use as the Promise reaction.
* This can be PROMISE_HANDLER_IDENTITY,
* PROMISE_HANDLER_THROWER, or a callable. In the
* latter case, it's guaranteed to be an object from
* the same compartment as the PromiseReactionJob.
* ReactionJobSlot_JobData: JobData - a, potentially CCW-wrapped, dense list
* containing data required for proper execution of
* the reaction.
*
* The JobData list has the following entries:
* ReactionJobDataSlot_HandlerArg: Value passed as argument when invoking the
* reaction handler.
* ReactionJobDataSlot_ResolveHook: The Promise's resolve hook, invoked if the
* handler is PROMISE_HANDLER_IDENTITY or
* upon successful execution of a callable
* handler.
* ReactionJobDataSlot_RejectHook: The Promise's reject hook, invoked if the
* handler is PROMISE_HANDLER_THROWER or if
* execution of a callable handler aborts
* abnormally.
*/
static bool
PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction job(cx, &args.callee().as<JSFunction>());
RootedNativeObject jobArgs(cx, &job->getExtendedSlot(0).toObject().as<NativeObject>());
RootedValue argument(cx, jobArgs->getDenseElement(1));
RootedFunction job(cx, &args.callee().as<JSFunction>());
RootedValue handlerVal(cx, job->getExtendedSlot(ReactionJobSlot_Handler));
RootedObject jobDataObj(cx, &job->getExtendedSlot(ReactionJobSlot_JobData).toObject());
// To ensure that the embedding ends up with the right entry global, we're
// guaranteeing that the reaction job function gets created in the same
// compartment as the handler function. That's not necessarily the global
// that the job was triggered from, though. To be able to find the
// triggering global, we always create the jobArgs object in that global
// and wrap it into the handler's. So to go back, we check if jobArgsObj
// is a wrapper and if so, unwrap it, enter its compartment, and wrap
// the handler into that compartment.
//
// See the doc comment for PromiseReactionJob for how this information is
// stored.
mozilla::Maybe<AutoCompartment> ac;
if (IsWrapper(jobDataObj)) {
jobDataObj = UncheckedUnwrap(jobDataObj);
ac.emplace(cx, jobDataObj);
if (!cx->compartment()->wrap(cx, &handlerVal))
return false;
}
RootedNativeObject jobData(cx, &jobDataObj->as<NativeObject>());
RootedValue argument(cx, jobData->getDenseElement(ReactionJobDataSlot_HandlerArg));
// Step 1 (omitted).
// Steps 2-3.
RootedValue handlerVal(cx, jobArgs->getDenseElement(0));
RootedValue handlerResult(cx);
bool shouldReject = false;
@ -486,31 +552,163 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
}
// Steps 7-9.
size_t hookSlot = shouldReject
? ReactionJobDataSlot_RejectHook
: ReactionJobDataSlot_ResolveHook;
RootedObject callee(cx, &jobData->getDenseElement(hookSlot).toObject());
FixedInvokeArgs<1> args2(cx);
args2[0].set(handlerResult);
RootedValue calleeOrRval(cx);
if (shouldReject) {
calleeOrRval = jobArgs->getDenseElement(3);
} else {
calleeOrRval = jobArgs->getDenseElement(2);
}
RootedValue calleeOrRval(cx, ObjectValue(*callee));
bool result = Call(cx, calleeOrRval, UndefinedHandleValue, args2, &calleeOrRval);
args.rval().set(calleeOrRval);
return result;
}
// ES6, 25.4.2.2.
bool
EnqueuePromiseReactionJob(JSContext* cx, HandleValue handler_, HandleValue handlerArg,
HandleObject resolve, HandleObject reject,
HandleObject promise_, HandleObject objectFromIncumbentGlobal_)
{
// Create a dense array to hold the data needed for the reaction job to
// work.
// See doc comment for PromiseReactionJob for layout details.
RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ReactionJobDataSlotsCount));
if (!data ||
data->ensureDenseElements(cx, 0, ReactionJobDataSlotsCount) != DenseElementResult::Success)
{
return false;
}
// Store the handler argument.
data->setDenseElement(ReactionJobDataSlot_HandlerArg, handlerArg);
// Store the resolve hook.
data->setDenseElement(ReactionJobDataSlot_ResolveHook, ObjectValue(*resolve));
// Store the reject hook.
data->setDenseElement(ReactionJobDataSlot_RejectHook, ObjectValue(*reject));
RootedValue dataVal(cx, ObjectValue(*data));
// Re-rooting because we might need to unwrap it.
RootedValue handler(cx, handler_);
// If we have a handler callback, we enter that handler's compartment so
// that the promise reaction job function is created in that compartment.
// That guarantees that the embedding ends up with the right entry global.
// This is relevant for some html APIs like fetch that derive information
// from said global.
mozilla::Maybe<AutoCompartment> ac;
if (handler.isObject()) {
RootedObject handlerObj(cx, &handler.toObject());
// The unwrapping has to be unchecked because we specifically want to
// be able to use handlers with wrappers that would only allow calls.
// E.g., it's ok to have a handler from a chrome compartment in a
// reaction to a content compartment's Promise instance.
handlerObj = UncheckedUnwrap(handlerObj);
MOZ_ASSERT(handlerObj);
ac.emplace(cx, handlerObj);
handler = ObjectValue(*handlerObj);
// We need to wrap the |data| array to store it on the job function.
if (!cx->compartment()->wrap(cx, &dataVal))
return false;
}
// Create the JS function to call when the job is triggered.
RootedAtom funName(cx, cx->names().empty);
RootedFunction job(cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName,
gc::AllocKind::FUNCTION_EXTENDED));
if (!job)
return false;
// Store the handler and the data array on the reaction job.
job->setExtendedSlot(ReactionJobSlot_Handler, handler);
job->setExtendedSlot(ReactionJobSlot_JobData, dataVal);
// When using JS::AddPromiseReactions, no actual promise is created, so we
// might not have one here.
// If we do, Wrap it in case we entered the handler's compartment above,
// because we should pass objects from a single compartment to the
// enqueuePromiseJob callback.
RootedObject promise(cx, promise_);
if (!cx->compartment()->wrap(cx, &promise))
return false;
// Using objectFromIncumbentGlobal, we can derive the incumbent global by
// unwrapping and then getting the global. This is very convoluted, but
// much better than having to store the original global as a private value
// because we couldn't wrap it to store it as a normal JS value.
RootedObject global(cx);
RootedObject objectFromIncumbentGlobal(cx, objectFromIncumbentGlobal_);
if (objectFromIncumbentGlobal) {
objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal);
MOZ_ASSERT(objectFromIncumbentGlobal);
global = &objectFromIncumbentGlobal->global();
}
// Note: the global we pass here might be from a different compartment
// than job and promise. While it's somewhat unusual to pass objects
// from multiple compartments, in this case we specifically need the
// global to be unwrapped because wrapping and unwrapping aren't
// necessarily symmetric for globals.
return cx->runtime()->enqueuePromiseJob(cx, job, promise, global);
}
enum ThenableJobSlots {
ThenableJobSlot_Handler = 0,
ThenableJobSlot_JobData,
};
enum ThenableJobDataSlots {
ThenableJobDataSlot_Promise = 0,
ThenableJobDataSlot_Thenable,
ThenableJobDataSlotsCount,
};
// ES6, 25.4.2.2.
/**
* Callback for resolving a thenable, to be invoked by the embedding during
* its processing of the Promise job queue.
*
* See http://www.ecma-international.org/ecma-262/6.0/index.html#sec-jobs-and-job-queues
*
* A PromiseResolveThenableJob is set as the native function of an extended
* JSFunction object, with all information required for the job's
* execution stored in the function's extended slots.
*
* Usage of the function's extended slots is as follows:
* ThenableJobSlot_Handler: The handler to use as the Promise reaction.
* This can be PROMISE_HANDLER_IDENTITY,
* PROMISE_HANDLER_THROWER, or a callable. In the
* latter case, it's guaranteed to be an object
* from the same compartment as the
* PromiseReactionJob.
* ThenableJobSlot_JobData: JobData - a, potentially CCW-wrapped, dense list
* containing data required for proper execution of
* the reaction.
*
* The JobData list has the following entries:
* ThenableJobDataSlot_Promise: The Promise to resolve using the given
* thenable.
* ThenableJobDataSlot_Thenable: The thenable to use as the receiver when
* calling the `then` function.
*/
static bool
PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction job(cx, &args.callee().as<JSFunction>());
RootedNativeObject jobArgs(cx, &job->getExtendedSlot(0).toObject().as<NativeObject>());
RootedValue promise(cx, jobArgs->getDenseElement(2));
RootedValue then(cx, jobArgs->getDenseElement(0));
RootedValue thenable(cx, jobArgs->getDenseElement(1));
RootedFunction job(cx, &args.callee().as<JSFunction>());
RootedValue then(cx, job->getExtendedSlot(ThenableJobSlot_Handler));
MOZ_ASSERT(!IsWrapper(&then.toObject()));
RootedNativeObject jobArgs(cx, &job->getExtendedSlot(ThenableJobSlot_JobData)
.toObject().as<NativeObject>());
RootedValue promise(cx, jobArgs->getDenseElement(ThenableJobDataSlot_Promise));
RootedValue thenable(cx, jobArgs->getDenseElement(ThenableJobDataSlot_Thenable));
// Step 1.
RootedValue resolveVal(cx);
@ -538,6 +736,64 @@ PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp)
return Call(cx, rejectVal, UndefinedHandleValue, rejectArgs, &rval);
}
bool
EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_,
HandleValue thenable_, HandleValue thenVal)
{
// Need to re-root these to enable wrapping them below.
RootedValue promiseToResolve(cx, promiseToResolve_);
RootedValue thenable(cx, thenable_);
// We enter the `then` callable's compartment so that the job function is
// created in that compartment.
// That guarantees that the embedding ends up with the right entry global.
// This is relevant for some html APIs like fetch that derive information
// from said global.
RootedObject then(cx, CheckedUnwrap(&thenVal.toObject()));
AutoCompartment ac(cx, then);
RootedAtom funName(cx, cx->names().empty);
if (!funName)
return false;
RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
gc::AllocKind::FUNCTION_EXTENDED));
if (!job)
return false;
// Store the `then` function on the callback.
job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then));
// Create a dense array to hold the data needed for the reaction job to
// work.
// See the doc comment for PromiseResolveThenableJob for the layout.
RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataSlotsCount));
if (!data ||
data->ensureDenseElements(cx, 0, ThenableJobDataSlotsCount) != DenseElementResult::Success)
{
return false;
}
// Wrap and set the `promiseToResolve` argument.
if (!cx->compartment()->wrap(cx, &promiseToResolve))
return false;
data->setDenseElement(ThenableJobDataSlot_Promise, promiseToResolve);
// At this point the promise is guaranteed to be wrapped into the job's
// compartment.
RootedObject promise(cx, &promiseToResolve.toObject());
// Wrap and set the `thenable` argument.
MOZ_ASSERT(thenable.isObject());
if (!cx->compartment()->wrap(cx, &thenable))
return false;
data->setDenseElement(ThenableJobDataSlot_Thenable, thenable);
// Store the data array on the reaction job.
job->setExtendedSlot(ThenableJobSlot_JobData, ObjectValue(*data));
RootedObject incumbentGlobal(cx, cx->runtime()->getIncumbentGlobal(cx));
return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal);
}
} // namespace js
static JSObject*

View File

@ -67,11 +67,36 @@ class PromiseObject : public NativeObject
}
};
// ES6, 25.4.2.1.
bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp);
/**
* 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.
*/
bool EnqueuePromiseReactionJob(JSContext* cx, HandleValue handler, HandleValue handlerArg,
HandleObject resolve, HandleObject reject,
HandleObject promise, HandleObject objectFromIncumbentGlobal);
// ES6, 25.4.2.2.
bool PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp);
/**
* 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);
} // namespace js

View File

@ -85,7 +85,7 @@ function CreateResolvingFunctions(promise) {
}
// Step 12.
EnqueuePromiseResolveThenableJob(promise, resolution, then);
_EnqueuePromiseResolveThenableJob(promise, resolution, then);
// Step 13.
return undefined;
@ -227,22 +227,15 @@ function TriggerPromiseReactions(reactions, argument) {
// ES6, 25.4.2.1.
function EnqueuePromiseReactionJob(reaction, argument) {
let capabilities = reaction.capabilities;
_EnqueuePromiseReactionJob([reaction.handler,
argument,
capabilities.resolve,
capabilities.reject
],
capabilities.promise);
_EnqueuePromiseReactionJob(reaction.handler,
argument,
capabilities.resolve,
capabilities.reject,
capabilities.promise,
reaction.incumbentGlobal || null);
}
// ES6, 25.4.2.2.
function EnqueuePromiseResolveThenableJob(promiseToResolve, thenable, then) {
_EnqueuePromiseResolveThenableJob([then,
thenable,
promiseToResolve
],
promiseToResolve);
}
// ES6, 25.4.2.2. (Implemented in C++).
// ES6, 25.4.3.1. (Implemented in C++).
@ -877,18 +870,21 @@ function PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability)
if (!IsCallable(onRejected))
onRejected = PROMISE_HANDLER_THROWER;
let incumbentGlobal = _GetObjectFromIncumbentGlobal();
// Step 5.
let fulfillReaction = {
__proto__: PromiseReactionRecordProto,
capabilities: resultCapability,
handler: onFulfilled
handler: onFulfilled,
incumbentGlobal
};
// Step 6.
let rejectReaction = {
__proto__: PromiseReactionRecordProto,
capabilities: resultCapability,
handler: onRejected
handler: onRejected,
incumbentGlobal
};
// Step 7.

View File

@ -4642,6 +4642,11 @@ JS_GetInterruptCallback(JSRuntime* rt)
/*
* Promises.
*/
JS_PUBLIC_API(void)
JS::SetGetIncumbentGlobalCallback(JSRuntime* rt, JSGetIncumbentGlobalCallback callback)
{
rt->getIncumbentGlobalCallback = callback;
}
JS_PUBLIC_API(void)
JS::SetEnqueuePromiseJobCallback(JSRuntime* rt, JSEnqueuePromiseJobCallback callback,

View File

@ -582,9 +582,13 @@ typedef void
typedef bool
(* JSInterruptCallback)(JSContext* cx);
typedef JSObject*
(* JSGetIncumbentGlobalCallback)(JSContext* cx);
typedef bool
(* JSEnqueuePromiseJobCallback)(JSContext* cx, JS::HandleObject job,
JS::HandleObject allocationSite, void* data);
JS::HandleObject allocationSite, JS::HandleObject incumbentGlobal,
void* data);
enum class PromiseRejectionHandlingState {
Unhandled,
@ -4314,6 +4318,16 @@ JS_RequestInterruptCallback(JSRuntime* rt);
namespace JS {
/**
* Sets the callback that's invoked whenever an incumbent global is required.
*
* SpiderMonkey doesn't itself have a notion of incumbent globals as defined
* by the html spec, so we need the embedding to provide this.
* See dom/base/ScriptSettings.h for details.
*/
extern JS_PUBLIC_API(void)
SetGetIncumbentGlobalCallback(JSRuntime* rt, JSGetIncumbentGlobalCallback callback);
/**
* Sets the callback that's invoked whenever a Promise job should be enqeued.
*

View File

@ -623,9 +623,15 @@ RunModule(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
}
#ifdef SPIDERMONKEY_PROMISE
static JSObject*
ShellGetIncumbentGlobalCallback(JSContext* cx)
{
return JS::CurrentGlobalOrNull(cx);
}
static bool
ShellEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject job, JS::HandleObject allocationSite,
void* data)
JS::HandleObject incumbentGlobal, void* data)
{
ShellRuntime* sr = GetShellRuntime(cx);
MOZ_ASSERT(job);
@ -2954,6 +2960,7 @@ WorkerMain(void* arg)
#ifdef SPIDERMONKEY_PROMISE
sr->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
JS::SetEnqueuePromiseJobCallback(rt, ShellEnqueuePromiseJobCallback);
JS::SetGetIncumbentGlobalCallback(rt, ShellGetIncumbentGlobalCallback);
#endif // SPIDERMONKEY_PROMISE
EnvironmentPreparer environmentPreparer(cx);
@ -2986,6 +2993,7 @@ WorkerMain(void* arg)
JS::SetLargeAllocationFailureCallback(rt, nullptr, nullptr);
#ifdef SPIDERMONKEY_PROMISE
JS::SetGetIncumbentGlobalCallback(rt, nullptr);
JS::SetEnqueuePromiseJobCallback(rt, nullptr);
sr->jobQueue.reset();
#endif // SPIDERMONKEY_PROMISE
@ -7404,6 +7412,7 @@ main(int argc, char** argv, char** envp)
#ifdef SPIDERMONKEY_PROMISE
sr->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
JS::SetEnqueuePromiseJobCallback(rt, ShellEnqueuePromiseJobCallback);
JS::SetGetIncumbentGlobalCallback(rt, ShellGetIncumbentGlobalCallback);
#endif // SPIDERMONKEY_PROMISE
EnvironmentPreparer environmentPreparer(cx);
@ -7435,6 +7444,7 @@ main(int argc, char** argv, char** envp)
JS::SetLargeAllocationFailureCallback(rt, nullptr, nullptr);
#ifdef SPIDERMONKEY_PROMISE
JS::SetGetIncumbentGlobalCallback(rt, nullptr);
JS::SetEnqueuePromiseJobCallback(rt, nullptr);
sr->jobQueue.reset();
#endif // SPIDERMONKEY_PROMISE

View File

@ -156,6 +156,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
handlingSegFault(false),
handlingJitInterrupt_(false),
interruptCallback(nullptr),
getIncumbentGlobalCallback(nullptr),
enqueuePromiseJobCallback(nullptr),
enqueuePromiseJobCallbackData(nullptr),
promiseRejectionTrackerCallback(nullptr),
@ -767,17 +768,35 @@ FreeOp::~FreeOp()
jit::ExecutableAllocator::poisonCode(runtime(), jitPoisonRanges);
}
JSObject*
JSRuntime::getIncumbentGlobal(JSContext* cx)
{
MOZ_ASSERT(cx->runtime()->getIncumbentGlobalCallback,
"Must set a callback using JS_SetGetIncumbentGlobalCallback before using Promises");
return cx->runtime()->getIncumbentGlobalCallback(cx);
}
bool
JSRuntime::enqueuePromiseJob(JSContext* cx, HandleFunction job, HandleObject promise)
JSRuntime::enqueuePromiseJob(JSContext* cx, HandleFunction job, HandleObject promise,
HandleObject incumbentGlobal)
{
MOZ_ASSERT(cx->runtime()->enqueuePromiseJobCallback,
"Must set a callback using JS_SetEnqeueuPromiseJobCallback before using Promises");
MOZ_ASSERT_IF(incumbentGlobal, !IsWrapper(incumbentGlobal) && !IsWindowProxy(incumbentGlobal));
void* data = cx->runtime()->enqueuePromiseJobCallbackData;
RootedObject allocationSite(cx);
if (promise)
allocationSite = JS::GetPromiseAllocationSite(promise);
return cx->runtime()->enqueuePromiseJobCallback(cx, job, allocationSite, data);
if (promise) {
RootedObject unwrappedPromise(cx, promise);
// While the job object is guaranteed to be unwrapped, the promise
// might be wrapped. See the comments in
// intrinsic_EnqueuePromiseReactionJob for details.
if (IsWrapper(promise))
unwrappedPromise = UncheckedUnwrap(promise);
allocationSite = JS::GetPromiseAllocationSite(unwrappedPromise);
}
return cx->runtime()->enqueuePromiseJobCallback(cx, job, allocationSite, incumbentGlobal, data);
}
void

View File

@ -934,6 +934,7 @@ struct JSRuntime : public JS::shadow::Runtime,
JSInterruptCallback interruptCallback;
JSGetIncumbentGlobalCallback getIncumbentGlobalCallback;
JSEnqueuePromiseJobCallback enqueuePromiseJobCallback;
void* enqueuePromiseJobCallbackData;
@ -1051,7 +1052,9 @@ struct JSRuntime : public JS::shadow::Runtime,
inline JSContext* unsafeContextFromAnyThread();
inline JSContext* contextFromMainThread();
bool enqueuePromiseJob(JSContext* cx, js::HandleFunction job, js::HandleObject promise);
JSObject* getIncumbentGlobal(JSContext* cx);
bool enqueuePromiseJob(JSContext* cx, js::HandleFunction job, js::HandleObject promise,
js::HandleObject incumbentGlobal);
void addUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise);
void removeUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise);

View File

@ -1793,70 +1793,53 @@ js::ReportIncompatibleSelfHostedMethod(JSContext* cx, const CallArgs& args)
return false;
}
// ES6, 25.4.1.6.
static bool
intrinsic_EnqueuePromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].toObject().as<NativeObject>().getDenseInitializedLength() == 4);
MOZ_ASSERT(args.length() == 6);
// When using JS::AddPromiseReactions, no actual promise is created, so we
// might not have one here.
RootedObject promise(cx);
if (args[1].isObject())
promise = UncheckedUnwrap(&args[1].toObject());
RootedValue handler(cx, args[0]);
MOZ_ASSERT((handler.isNumber() &&
(handler.toNumber() == PROMISE_HANDLER_IDENTITY ||
handler.toNumber() == PROMISE_HANDLER_THROWER)) ||
handler.toObject().isCallable());
#ifdef DEBUG
MOZ_ASSERT_IF(promise, promise->is<PromiseObject>());
RootedNativeObject jobArgs(cx, &args[0].toObject().as<NativeObject>());
MOZ_ASSERT((jobArgs->getDenseElement(0).isNumber() &&
(jobArgs->getDenseElement(0).toNumber() == PROMISE_HANDLER_IDENTITY ||
jobArgs->getDenseElement(0).toNumber() == PROMISE_HANDLER_THROWER)) ||
jobArgs->getDenseElement(0).toObject().isCallable());
MOZ_ASSERT(jobArgs->getDenseElement(2).toObject().isCallable());
MOZ_ASSERT(jobArgs->getDenseElement(3).toObject().isCallable());
#endif
RootedValue handlerArg(cx, args[1]);
RootedAtom funName(cx, cx->names().empty);
RootedFunction job(cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName,
gc::AllocKind::FUNCTION_EXTENDED));
if (!job)
return false;
job->setExtendedSlot(0, args[0]);
if (!cx->runtime()->enqueuePromiseJob(cx, job, promise))
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;
}
// ES6, 25.4.1.6.
static bool
intrinsic_EnqueuePromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
#ifdef DEBUG
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(UncheckedUnwrap(&args[1].toObject())->is<PromiseObject>());
RootedNativeObject jobArgs(cx, &args[0].toObject().as<NativeObject>());
MOZ_ASSERT(jobArgs->getDenseInitializedLength() == 3);
MOZ_ASSERT(jobArgs->getDenseElement(0).toObject().isCallable());
MOZ_ASSERT(jobArgs->getDenseElement(1).isObject());
MOZ_ASSERT(UncheckedUnwrap(&jobArgs->getDenseElement(2).toObject())->is<PromiseObject>());
#endif
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(IsCallable(args[2]));
RootedAtom funName(cx, cx->names().empty);
RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
gc::AllocKind::FUNCTION_EXTENDED));
if (!job)
RootedValue promiseToResolve(cx, args[0]);
RootedValue thenable(cx, args[1]);
RootedValue then(cx, args[2]);
if (!EnqueuePromiseResolveThenableJob(cx, promiseToResolve, thenable, then))
return false;
job->setExtendedSlot(0, args[0]);
RootedObject promise(cx, CheckedUnwrap(&args[1].toObject()));
if (!cx->runtime()->enqueuePromiseJob(cx, job, promise))
return false;
args.rval().setUndefined();
return true;
}
@ -2009,6 +1992,48 @@ intrinsic_OriginalPromiseConstructor(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_RejectUnwrappedPromise(JSContext* cx, unsigned argc, Value* vp)
{
@ -2499,6 +2524,7 @@ 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),

View File

@ -547,6 +547,8 @@ CycleCollectedJSRuntime::Initialize(JSRuntime* aParentRuntime,
SetDOMCallbacks(mJSRuntime, &DOMcallbacks);
js::SetScriptEnvironmentPreparer(mJSRuntime, &mEnvironmentPreparer);
JS::SetGetIncumbentGlobalCallback(mJSRuntime, GetIncumbentGlobalCallback);
#ifdef SPIDERMONKEY_PROMISE
JS::SetEnqueuePromiseJobCallback(mJSRuntime, EnqueuePromiseJobCallback, this);
JS::SetPromiseRejectionTrackerCallback(mJSRuntime, PromiseRejectionTrackerCallback, this);
@ -920,8 +922,9 @@ CycleCollectedJSRuntime::LargeAllocationFailureCallback(void* aData)
class PromiseJobRunnable final : public Runnable
{
public:
PromiseJobRunnable(JS::HandleObject aCallback, JS::HandleObject aAllocationSite)
: mCallback(new PromiseJobCallback(aCallback, aAllocationSite, nullptr))
PromiseJobRunnable(JS::HandleObject aCallback, JS::HandleObject aAllocationSite,
nsIGlobalObject* aIncumbentGlobal)
: mCallback(new PromiseJobCallback(aCallback, aAllocationSite, aIncumbentGlobal))
{
}
@ -944,18 +947,34 @@ private:
RefPtr<PromiseJobCallback> mCallback;
};
/* static */
JSObject*
CycleCollectedJSRuntime::GetIncumbentGlobalCallback(JSContext* aCx)
{
nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal();
if (global) {
return global->GetGlobalJSObject();
}
return nullptr;
}
/* static */
bool
CycleCollectedJSRuntime::EnqueuePromiseJobCallback(JSContext* aCx,
JS::HandleObject aJob,
JS::HandleObject aAllocationSite,
JS::HandleObject aIncumbentGlobal,
void* aData)
{
CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
MOZ_ASSERT(JS_GetRuntime(aCx) == self->Runtime());
MOZ_ASSERT(Get() == self);
nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite);
nsIGlobalObject* global = nullptr;
if (aIncumbentGlobal) {
global = xpc::NativeGlobal(aIncumbentGlobal);
}
nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global);
self->DispatchToMicroTask(runnable);
return true;
}

View File

@ -217,9 +217,11 @@ private:
static void LargeAllocationFailureCallback(void* aData);
static bool ContextCallback(JSContext* aCx, unsigned aOperation,
void* aData);
static JSObject* GetIncumbentGlobalCallback(JSContext* aCx);
static bool EnqueuePromiseJobCallback(JSContext* aCx,
JS::HandleObject aJob,
JS::HandleObject aAllocationSite,
JS::HandleObject aIncumbentGlobal,
void* aData);
#ifdef SPIDERMONKEY_PROMISE
static void PromiseRejectionTrackerCallback(JSContext* aCx,