Bug 1243001 part 6. Implement Promise::AppendNativeHandler in the SPIDERMONKEY_PROMISE world. r=peterv

This patch introduces a fake IDL interface just to get the benefits of
cycle collection for the JS-to-C++ link we now need for PromiseNativeHandler
(because the SpiderMonkey Promise somehow needs to point to the
PromiseNativeHandler).  Now in practice a bunch of our PromiseNativeHandlers
are not cycle collected.  That kinda freaks me out, but spot-checking a few
suggests they do not in fact leak (either because they don't form cycles or
because the Promise they're observing always settles and then releases them).
Either way, that's a problem that exists with or without this patch...
This commit is contained in:
Boris Zbarsky 2016-02-09 17:40:31 -05:00
parent 3366af6c39
commit fbe6dcf99f
6 changed files with 144 additions and 2 deletions

View File

@ -969,6 +969,10 @@ DOMInterfaces = {
'concrete': False,
},
'PromiseNativeHandler': {
'wrapperCache': False,
},
'PropertyNodeList': {
'headerFile': 'HTMLPropertiesCollection.h',
},

View File

@ -698,10 +698,108 @@ Promise::MaybeReject(JSContext* aCx,
}
}
#define SLOT_NATIVEHANDLER 0
#define SLOT_NATIVEHANDLER_TASK 1
enum class NativeHandlerTask : int32_t {
Resolve,
Reject
};
static bool
NativeHandlerCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
{
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
JS::Rooted<JS::Value> v(aCx,
js::GetFunctionNativeReserved(&args.callee(),
SLOT_NATIVEHANDLER));
MOZ_ASSERT(v.isObject());
PromiseNativeHandler* handler;
if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &v.toObject(),
handler))) {
return Throw(aCx, NS_ERROR_UNEXPECTED);
}
v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
if (task == NativeHandlerTask::Resolve) {
handler->ResolvedCallback(aCx, args.get(0));
} else {
MOZ_ASSERT(task == NativeHandlerTask::Reject);
handler->RejectedCallback(aCx, args.get(0));
}
return true;
}
static JSObject*
CreateNativeHandlerFunction(JSContext* aCx, JS::Handle<JSObject*> aHolder,
NativeHandlerTask aTask)
{
JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
/* nargs = */ 1,
/* flags = */ 0, nullptr);
if (!func) {
return nullptr;
}
JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
JS::ExposeObjectToActiveJS(aHolder);
js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER,
JS::ObjectValue(*aHolder));
js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER_TASK,
JS::Int32Value(static_cast<int32_t>(aTask)));
return obj;
}
void
Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable)
{
// XXXbz Implementation coming up in the next diff.
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
// Our API doesn't allow us to return a useful error. Not like this should
// happen anyway.
return;
}
jsapi.TakeOwnershipOfErrorReporting();
JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> handlerWrapper(cx);
// Note: PromiseNativeHandler is NOT wrappercached. So we can't use
// ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
if (NS_WARN_IF(!aRunnable->WrapObject(cx, nullptr, &handlerWrapper))) {
// Again, no way to report errors.
jsapi.ClearException();
return;
}
JS::Rooted<JSObject*> resolveFunc(cx);
resolveFunc =
CreateNativeHandlerFunction(cx, handlerWrapper, NativeHandlerTask::Resolve);
if (NS_WARN_IF(!resolveFunc)) {
jsapi.ClearException();
return;
}
JS::Rooted<JSObject*> rejectFunc(cx);
rejectFunc =
CreateNativeHandlerFunction(cx, handlerWrapper, NativeHandlerTask::Reject);
if (NS_WARN_IF(!rejectFunc)) {
jsapi.ClearException();
return;
}
JS::Rooted<JSObject*> promiseObj(cx, GetWrapper());
if (NS_WARN_IF(!JS::AddPromiseReactions(cx, promiseObj, resolveFunc,
rejectFunc))) {
jsapi.ClearException();
return;
}
}
void

View File

@ -0,0 +1,26 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/PromiseBinding.h"
namespace mozilla {
namespace dom {
#ifdef SPIDERMONKEY_PROMISE
bool
PromiseNativeHandler::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aWrapper)
{
return PromiseNativeHandlerBinding::Wrap(aCx, this, aGivenProto, aWrapper);
}
#endif // SPIDERMONKEY_PROMISE
} // namespace dom
} // namespace mozilla

View File

@ -30,6 +30,12 @@ public:
virtual void
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) = 0;
#ifdef SPIDERMONKEY_PROMISE
bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aWrapper);
#endif // SPIDERMONKEY_PROMISE
};
} // namespace dom

View File

@ -14,7 +14,8 @@ EXPORTS.mozilla.dom += [
UNIFIED_SOURCES += [
'Promise.cpp',
'PromiseCallback.cpp',
'PromiseDebugging.cpp'
'PromiseDebugging.cpp',
'PromiseNativeHandler.cpp',
]
LOCAL_INCLUDES += [

View File

@ -65,4 +65,11 @@ interface _Promise {
// Need to escape "Promise" so it's treated as an identifier.
interface _Promise {
};
// Hack to allow us to have JS owning and properly tracing/CCing/etc a
// PromiseNativeHandler.
[NoInterfaceObject,
Exposed=(Window,Worker,System)]
interface PromiseNativeHandler {
};
#endif // SPIDERMONKEY_PROMISE