Bug 1170760 part 5. Implement NewPromiseCapability which can either return a PromiseCapability as in the spec, or one that has a native promise and maybe resolve/reject functions if the consumer asked for them. r=baku,efaust

This commit is contained in:
Boris Zbarsky 2015-11-25 15:48:09 -05:00
Родитель 56a0511eb4
Коммит aaff15aaaa
3 изменённых файлов: 202 добавлений и 0 удалений

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

@ -83,5 +83,9 @@ MSG_DEF(MSG_NOTIFICATION_PERMISSION_DENIED, 0, JSEXN_TYPEERR, "Permission to sho
MSG_DEF(MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER, 0, JSEXN_TYPEERR, "Notification constructor cannot be used in ServiceWorkerGlobalScope. Use registration.showNotification() instead.")
MSG_DEF(MSG_INVALID_SCOPE, 2, JSEXN_TYPEERR, "Invalid scope trying to resolve {0} with base URL {1}.")
MSG_DEF(MSG_INVALID_KEYFRAME_OFFSETS, 0, JSEXN_TYPEERR, "Keyframes with specified offsets must be in order and all be in the range [0, 1].")
MSG_DEF(MSG_ILLEGAL_PROMISE_CONSTRUCTOR, 0, JSEXN_TYPEERR, "Non-constructor value passed to NewPromiseCapability.")
MSG_DEF(MSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.")
MSG_DEF(MSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
MSG_DEF(MSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
MSG_DEF(MSG_SW_INSTALL_ERROR, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} encountered an error during installation.")
MSG_DEF(MSG_SW_SCRIPT_THREW, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} threw an exception during script evaluation.")

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

@ -34,6 +34,7 @@
#include "PromiseWorkerProxy.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "WrapperFactory.h"
#include "xpcpublic.h"
#ifdef MOZ_CRASHREPORTER
#include "nsExceptionHandler.h"
@ -817,6 +818,192 @@ Promise::CallInitFunction(const GlobalObject& aGlobal,
}
}
#define GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT 0
#define GET_CAPABILITIES_EXECUTOR_REJECT_SLOT 1
namespace {
bool
GetCapabilitiesExecutor(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
{
// Implements
// http://www.ecma-international.org/ecma-262/6.0/#sec-getcapabilitiesexecutor-functions
// except we store the [[Resolve]] and [[Reject]] in our own internal slots,
// not in a PromiseCapability. The PromiseCapability will then read them from
// us.
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
// Step 1 is an assert.
// Step 2 doesn't need to be done, because it's just giving a name to the
// PromiseCapability record which is supposed to be stored in an internal
// slot. But we don't store that at all, per the comment above; we just
// directly store its [[Resolve]] and [[Reject]] members.
// Steps 3 and 4.
if (!js::GetFunctionNativeReserved(&args.callee(),
GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT).isUndefined() ||
!js::GetFunctionNativeReserved(&args.callee(),
GET_CAPABILITIES_EXECUTOR_REJECT_SLOT).isUndefined()) {
ErrorResult rv;
rv.ThrowTypeError<MSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY>();
return !rv.MaybeSetPendingException(aCx);
}
// Step 5.
js::SetFunctionNativeReserved(&args.callee(),
GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT,
args.get(0));
// Step 6.
js::SetFunctionNativeReserved(&args.callee(),
GET_CAPABILITIES_EXECUTOR_REJECT_SLOT,
args.get(1));
// Step 7.
args.rval().setUndefined();
return true;
}
} // anonymous namespace
/* static */ void
Promise::NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal,
JS::Handle<JS::Value> aConstructor,
bool aForceCallbackCreation,
PromiseCapability& aCapability,
ErrorResult& aRv)
{
// Implements
// http://www.ecma-international.org/ecma-262/6.0/#sec-newpromisecapability
if (!aConstructor.isObject() ||
!JS::IsConstructor(&aConstructor.toObject())) {
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
return;
}
// Step 2 is a note.
// Step 3 is already done because we got the PromiseCapability passed in.
// Optimization: Check whether constructor is in fact the canonical
// Promise constructor for aGlobal.
JS::Rooted<JSObject*> global(aCx, aGlobal->GetGlobalJSObject());
{
// Scope for the JSAutoCompartment, since we need to enter the compartment
// of global to get constructors from it. Save the compartment we used to
// be in, though; we'll need it later.
JS::Rooted<JSObject*> callerGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
JSAutoCompartment ac(aCx, global);
// Now wrap aConstructor into the compartment of aGlobal, so comparing it to
// the canonical Promise for that compartment actually makes sense.
JS::Rooted<JS::Value> constructorValue(aCx, aConstructor);
if (!MaybeWrapObjectValue(aCx, &constructorValue)) {
aRv.NoteJSContextException();
return;
}
JSObject* defaultCtor = PromiseBinding::GetConstructorObject(aCx, global);
if (!defaultCtor) {
aRv.NoteJSContextException();
return;
}
if (defaultCtor == &constructorValue.toObject()) {
// This is the canonical Promise constructor.
aCapability.mNativePromise = Promise::Create(aGlobal, aRv);
if (aForceCallbackCreation) {
// We have to be a bit careful here. We want to create these functions
// in the compartment in which they would be created if we actually
// invoked the constructor via JS::Construct below. That means our
// callerGlobal compartment if aConstructor is an Xray and the reflector
// compartment of the promise we're creating otherwise. But note that
// our callerGlobal compartment is precisely the reflector compartment
// unless the call was done over Xrays, because the reflector
// compartment comes from xpc::XrayAwareCalleeGlobal. So we really just
// want to create these functions in the callerGlobal compartment.
MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(&aConstructor.toObject()) ||
callerGlobal == global);
JSAutoCompartment ac2(aCx, callerGlobal);
JSObject* resolveFuncObj =
CreateFunction(aCx, aCapability.mNativePromise,
PromiseCallback::Resolve);
if (!resolveFuncObj) {
aRv.NoteJSContextException();
return;
}
aCapability.mResolve.setObject(*resolveFuncObj);
JSObject* rejectFuncObj =
CreateFunction(aCx, aCapability.mNativePromise,
PromiseCallback::Reject);
if (!rejectFuncObj) {
aRv.NoteJSContextException();
return;
}
aCapability.mReject.setObject(*rejectFuncObj);
}
return;
}
}
// Step 4.
// We can create our get-capabilities function in the calling compartment. It
// will work just as if we did |new promiseConstructor(function(a,b){}).
// Notably, if we're called over Xrays that's all fine, because we will end up
// creating the callbacks in the caller compartment in that case.
JSFunction* getCapabilitiesFunc =
js::NewFunctionWithReserved(aCx, GetCapabilitiesExecutor,
2 /* nargs */,
0 /* flags */,
nullptr);
if (!getCapabilitiesFunc) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
JS::Rooted<JSObject*> getCapabilitiesObj(aCx);
getCapabilitiesObj = JS_GetFunctionObject(getCapabilitiesFunc);
// Step 5 doesn't need to be done, since we're not actually storing a
// PromiseCapability in the executor; see the comments in
// GetCapabilitiesExecutor above.
// Step 6 and step 7.
JS::Rooted<JS::Value> getCapabilities(aCx,
JS::ObjectValue(*getCapabilitiesObj));
JS::Rooted<JS::Value> promiseVal(aCx);
if (!JS::Construct(aCx, aConstructor,
JS::HandleValueArray(getCapabilities),
&promiseVal)) {
aRv.NoteJSContextException();
return;
}
// Step 8 plus copying over the value to the PromiseCapability.
JS::Rooted<JS::Value> v(aCx);
v = js::GetFunctionNativeReserved(getCapabilitiesObj,
GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT);
if (!v.isObject() || !JS::IsCallable(&v.toObject())) {
aRv.ThrowTypeError<MSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE>();
return;
}
aCapability.mResolve = v;
// Step 9 plus copying over the value to the PromiseCapability.
v = js::GetFunctionNativeReserved(getCapabilitiesObj,
GET_CAPABILITIES_EXECUTOR_REJECT_SLOT);
if (!v.isObject() || !JS::IsCallable(&v.toObject())) {
aRv.ThrowTypeError<MSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE>();
return;
}
aCapability.mReject = v;
// Step 10.
aCapability.mPromise = promiseVal;
// Step 11 doesn't need anything, since the PromiseCapability was passed in.
}
/* static */ already_AddRefed<Promise>
Promise::Resolve(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
JS::Handle<JS::Value> aValue, ErrorResult& aRv)

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

@ -233,6 +233,17 @@ protected:
void CallInitFunction(const GlobalObject& aGlobal, PromiseInit& aInit,
ErrorResult& aRv);
// The NewPromiseCapability function from
// <http://www.ecma-international.org/ecma-262/6.0/#sec-newpromisecapability>.
// Errors are communicated via aRv. If aForceCallbackCreation is
// true, then this function will ensure that aCapability has a
// useful mResolve/mReject even if mNativePromise is non-null.
static void NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal,
JS::Handle<JS::Value> aConstructor,
bool aForceCallbackCreation,
PromiseCapability& aCapability,
ErrorResult& aRv);
bool IsPending()
{
return mResolvePending;