Bug 983300 part 2. Introduce a GenericPromiseReturningBindingMethod for methods that return Promise return value. r=khuey,bholley

This method effectively catches exceptions from GenericBindingMethod and converts them into rejected promises.  This handles calls to Promise-returning APIs from everything except Ion fast paths.
This commit is contained in:
Boris Zbarsky 2014-03-21 12:18:24 -04:00
Родитель f40021934e
Коммит a4309c8b4e
4 изменённых файлов: 105 добавлений и 1 удалений

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

@ -37,6 +37,7 @@
#include "mozilla/dom/HTMLSharedObjectElement.h"
#include "mozilla/dom/HTMLEmbedElementBinding.h"
#include "mozilla/dom/HTMLAppletElementBinding.h"
#include "mozilla/dom/Promise.h"
#include "WorkerPrivate.h"
namespace mozilla {
@ -2257,5 +2258,77 @@ GenericBindingMethod(JSContext* cx, unsigned argc, JS::Value* vp)
return method(cx, obj, self, JSJitMethodCallArgs(args));
}
bool
GenericPromiseReturningBindingMethod(JSContext* cx, unsigned argc, JS::Value* vp)
{
// Make sure to save the callee before someone maybe messes with rval().
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::Rooted<JSObject*> callee(cx, &args.callee());
// We could invoke GenericBindingMethod here, but that involves an
// extra call. Manually inline it instead.
const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
if (!args.thisv().isObject()) {
ThrowInvalidThis(cx, args,
MSG_METHOD_THIS_DOES_NOT_IMPLEMENT_INTERFACE,
protoID);
return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
args.rval());
}
JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
void* self;
{
nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
if (NS_FAILED(rv)) {
ThrowInvalidThis(cx, args,
GetInvalidThisErrorForMethod(rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO),
protoID);
return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
args.rval());
}
}
MOZ_ASSERT(info->type() == JSJitInfo::Method);
JSJitMethodOp method = info->method;
bool ok = method(cx, obj, self, JSJitMethodCallArgs(args));
if (ok) {
return true;
}
return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
args.rval());
}
bool
ConvertExceptionToPromise(JSContext* cx,
JSObject* promiseScope,
JS::MutableHandle<JS::Value> rval)
{
GlobalObject global(cx, promiseScope);
if (global.Failed()) {
return false;
}
JS::Rooted<JS::Value> exn(cx);
if (!JS_GetPendingException(cx, &exn)) {
return false;
}
JS_ClearPendingException(cx);
ErrorResult rv;
nsRefPtr<Promise> promise = Promise::Reject(global, cx, exn, rv);
if (rv.Failed()) {
// We just give up. Make sure to not leak memory on the
// ErrorResult, but then just put the original exception back.
ThrowMethodFailedWithDetails(cx, rv, "", "");
JS_SetPendingException(cx, exn);
return false;
}
JS::Rooted<JSObject*> wrapScope(cx, JS::CurrentGlobalOrNull(cx));
return WrapNewBindingObject(cx, wrapScope, promise, rval);
}
} // namespace dom
} // namespace mozilla

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

@ -2457,6 +2457,22 @@ GenericBindingSetter(JSContext* cx, unsigned argc, JS::Value* vp);
bool
GenericBindingMethod(JSContext* cx, unsigned argc, JS::Value* vp);
bool
GenericPromiseReturningBindingMethod(JSContext* cx, unsigned argc, JS::Value* vp);
// ConvertExceptionToPromise should only be called when we have an error
// condition (e.g. returned false from a JSAPI method). Note that there may be
// no exception on cx, in which case this is an uncatchable failure that will
// simply be propagated. Otherwise this method will attempt to convert the
// exception to a Promise rejected with the exception that it will store in
// rval.
//
// promiseScope should be the scope in which the Promise should be created.
bool
ConvertExceptionToPromise(JSContext* cx,
JSObject* promiseScope,
JS::MutableHandle<JS::Value> rval);
} // namespace dom
} // namespace mozilla

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

@ -1656,7 +1656,9 @@ class MethodDefiner(PropertyDefiner):
"length": methodLength(m),
"flags": "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(m),
"allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable")}
"allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
"returnsPromise": m.returnsPromise(),
}
if isChromeOnly(m):
self.chrome.append(method)
else:
@ -1734,9 +1736,19 @@ class MethodDefiner(PropertyDefiner):
# JSTypedMethodJitInfo.
jitinfo = ("reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor)
if m.get("allowCrossOriginThis", False):
if m.get("returnsPromise", False):
raise TypeError("%s returns a Promise but should "
"be allowed cross-origin?" %
accessor)
accessor = "genericCrossOriginMethod"
elif self.descriptor.needsSpecialGenericOps():
if m.get("returnsPromise", False):
raise TypeError("%s returns a Promise but needs "
"special generic ops?" %
accessor)
accessor = "genericMethod"
elif m.get("returnsPromise", False):
accessor = "GenericPromiseReturningBindingMethod"
else:
accessor = "GenericBindingMethod"
else:

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

@ -3431,6 +3431,9 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
[attr.location])
IDLInterfaceMember.handleExtendedAttribute(self, attr)
def returnsPromise(self):
return self._overloads[0].returnType.isPromise()
def _getDependentObjects(self):
deps = set()
for overload in self._overloads: