mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
Bug 911216 - Part 27: Properly set up incumbent and current globals for Promise reaction jobs. r=efaust,bz
This commit is contained in:
parent
1eabe5f054
commit
14a62c9039
@ -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*
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user