Bug 865951. If the return value of a JS-implemented method or attribute is a JS-implemented interface and the returned object is not a DOM object, automatically wrap it up in an instance of that interface. r=mccr8

This does not invoke __init on the chrome object, since there are no
arguments to do it with anyway.  It also doesn't invoke init(), so
don't do this in cases in which the chrome-side object will need to
know its window.
This commit is contained in:
Boris Zbarsky 2013-05-20 13:47:08 -04:00
parent 182d5fd80d
commit 0307d91885
3 changed files with 122 additions and 43 deletions

View File

@ -1770,5 +1770,40 @@ Date::ToDateObject(JSContext* cx, JS::Value* vp) const
return true;
}
bool
GetWindowForJSImplementedObject(JSContext* cx, JS::Handle<JSObject*> obj,
nsPIDOMWindow** window)
{
// Be very careful to not get tricked here.
MOZ_ASSERT(NS_IsMainThread());
if (!xpc::AccessCheck::isChrome(js::GetObjectCompartment(obj))) {
NS_RUNTIMEABORT("Should have a chrome object here");
}
// Look up the content-side object.
JS::Rooted<JS::Value> domImplVal(cx);
if (!JS_GetProperty(cx, obj, "__DOM_IMPL__", domImplVal.address())) {
return false;
}
if (!domImplVal.isObject()) {
ThrowErrorMessage(cx, MSG_NOT_OBJECT);
return false;
}
// Go ahead and get the global from it. GlobalObject will handle
// doing unwrapping as needed.
GlobalObject global(cx, &domImplVal.toObject());
if (global.Failed()) {
return false;
}
// It's OK if we have null here: that just means the content-side
// object really wasn't associated with any window.
nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(global.Get()));
win.forget(window);
return true;
}
} // namespace dom
} // namespace mozilla

View File

@ -25,6 +25,8 @@
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/CallbackObject.h"
class nsPIDOMWindow;
namespace mozilla {
namespace dom {
@ -1991,6 +1993,13 @@ GetUnforgeableHolder(JSObject* aGlobal, prototypes::ID aId)
DOM_INTERFACE_PROTO_SLOTS_BASE).toObject();
}
// Given a JSObject* that represents the chrome side of a JS-implemented WebIDL
// interface, get the nsPIDOMWindow corresponding to the content side, if any.
// A false return means an exception was thrown.
bool
GetWindowForJSImplementedObject(JSContext* cx, JS::Handle<JSObject*> obj,
nsPIDOMWindow** window);
} // namespace dom
} // namespace mozilla

View File

@ -2287,13 +2287,21 @@ class CastableObjectUnwrapper():
called by the name in the "target" argument.
codeOnFailure is the code to run if unwrapping fails.
If isCallbackReturnValue is "JSImpl" and our descriptor is also
JS-implemented, fall back to just creating the right object if what we
have isn't one already.
"""
def __init__(self, descriptor, source, target, codeOnFailure):
def __init__(self, descriptor, source, target, codeOnFailure,
exceptionCode=None, isCallbackReturnValue=False):
if not exceptionCode:
exceptionCode = codeOnFailure
self.substitution = { "type" : descriptor.nativeType,
"protoID" : "prototypes::id::" + descriptor.name,
"source" : source,
"target" : target,
"codeOnFailure" : CGIndenter(CGGeneric(codeOnFailure), 4).define() }
"codeOnFailure" : CGIndenter(CGGeneric(codeOnFailure)).define(),
"exceptionCode" : CGIndenter(CGGeneric(exceptionCode), 4).define() }
if descriptor.hasXPConnectImpls:
# We don't use xpc_qsUnwrapThis because it will always throw on
# unwrap failure, whereas we want to control whether we throw or
@ -2311,6 +2319,26 @@ class CastableObjectUnwrapper():
"// We should have an object, too!\n"
"MOZ_ASSERT(objPtr);\n"
"${target} = objPtr;").substitute(self.substitution)), 4).define()
elif (isCallbackReturnValue == "JSImpl" and
descriptor.interface.isJSImplemented()):
self.substitution["codeOnFailure"] = CGIndenter(CGGeneric(string.Template(
"// Be careful to not wrap random DOM objects here, even if\n"
"// they're wrapped in opaque security wrappers for some reason.\n"
"// XXXbz Wish we could check for a JS-implemented object\n"
"// that already has a content reflection...\n"
"if (!IsDOMObject(js::UncheckedUnwrap(${source}))) {\n"
" nsCOMPtr<nsPIDOMWindow> ourWindow;\n"
" if (!GetWindowForJSImplementedObject(cx, Callback(), getter_AddRefs(ourWindow))) {\n"
"${exceptionCode}\n"
" }\n"
" JS::Rooted<JSObject*> jsImplSourceObj(cx, ${source});\n"
" ${target} = new ${type}(jsImplSourceObj, ourWindow);\n"
"} else {\n"
"${codeOnFailure}\n"
"}").substitute(self.substitution)), 4).define()
else:
self.substitution["codeOnFailure"] = CGIndenter(
CGGeneric(self.substitution["codeOnFailure"])).define()
def __str__(self):
return string.Template(
@ -2325,11 +2353,14 @@ class FailureFatalCastableObjectUnwrapper(CastableObjectUnwrapper):
"""
As CastableObjectUnwrapper, but defaulting to throwing if unwrapping fails
"""
def __init__(self, descriptor, source, target, exceptionCode):
def __init__(self, descriptor, source, target, exceptionCode,
isCallbackReturnValue):
CastableObjectUnwrapper.__init__(
self, descriptor, source, target,
'ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s");\n'
'%s' % (descriptor.name, exceptionCode))
'%s' % (descriptor.name, exceptionCode),
exceptionCode,
isCallbackReturnValue)
class CallbackObjectUnwrapper:
"""
@ -2519,9 +2550,13 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
If allowTreatNonCallableAsNull is true, then [TreatNonCallableAsNull]
extended attributes on nullable callback functions will be honored.
If isCallbackReturnValue is true, then the declType may be adjusted to make
it easier to return from a callback. Since that type is never directly
observable by any consumers of the callback code, this is OK.
If isCallbackReturnValue is "JSImpl" or "Callback", then the declType may be
adjusted to make it easier to return from a callback. Since that type is
never directly observable by any consumers of the callback code, this is OK.
Furthermore, if isCallbackReturnValue is "JSImpl", that affects the behavior
of the FailureFatalCastableObjectUnwrapper conversion; this is used for
implementing auto-wrapping of JS-implemented return values from a
JS-implemented interface.
The return value from this function is a JSToNativeConversionInfo.
"""
@ -2998,9 +3033,14 @@ for (uint32_t i = 0; i < length; ++i) {
isCallbackReturnValue)
# Sequences and non-worker callbacks have to hold a strong ref to the
# thing being passed down.
forceOwningType = (descriptor.interface.isCallback() and
not descriptor.workers) or isMember
# thing being passed down. Also, callback return values always end up
# addrefing anyway, so there is no point trying to avoid it here and it
# makes other things simpler since we can assume the return value is a
# strong ref.
forceOwningType = ((descriptor.interface.isCallback() and
not descriptor.workers) or
isMember or
isCallbackReturnValue)
typeName = descriptor.nativeType
typePtr = typeName + "*"
@ -3050,7 +3090,8 @@ for (uint32_t i = 0; i < length; ++i) {
descriptor,
"&${val}.toObject()",
"${declName}",
exceptionCode))
exceptionCode,
isCallbackReturnValue))
elif descriptor.workers:
return handleJSObjectType(type, isMember, failureCode)
else:
@ -8275,7 +8316,7 @@ class CGBindingRoot(CGThing):
return self.root.deps()
class CGNativeMember(ClassMethod):
def __init__(self, descriptor, member, name, signature, extendedAttrs,
def __init__(self, descriptorProvider, member, name, signature, extendedAttrs,
breakAfter=True, passCxAsNeeded=True, visibility="public",
jsObjectsArePtr=False, variadicIsSequence=False):
"""
@ -8286,10 +8327,10 @@ class CGNativeMember(ClassMethod):
JSContext* based on the return and argument types. We can
still pass it based on 'implicitJSContext' annotations.
"""
self.descriptor = descriptor
self.descriptorProvider = descriptorProvider
self.member = member
self.extendedAttrs = extendedAttrs
self.resultAlreadyAddRefed = isResultAlreadyAddRefed(self.descriptor,
self.resultAlreadyAddRefed = isResultAlreadyAddRefed(self.descriptorProvider,
self.extendedAttrs)
self.passCxAsNeeded = passCxAsNeeded
self.jsObjectsArePtr = jsObjectsArePtr
@ -8354,7 +8395,7 @@ class CGNativeMember(ClassMethod):
return enumName, defaultValue, "return ${declName};"
if type.isGeckoInterface():
iface = type.unroll().inner;
nativeType = self.descriptor.getDescriptor(
nativeType = self.descriptorProvider.getDescriptor(
iface.identifier.name).nativeType
# Now trim off unnecessary namespaces
nativeType = nativeType.split("::")
@ -8377,25 +8418,10 @@ class CGNativeMember(ClassMethod):
post=">")
else:
result = CGWrapper(result, post="*")
if iface.isExternal():
# The holder is an nsRefPtr. If we're nullable and end up null,
# the holder will be null anyway, so it's safe to just always
# return it here.
returnCode = ("(void)${declName}; // avoid warning. May end up not being read\n"
"return ${holderName}.forget();")
elif iface.isCallback():
# The decl is an OwningNonNull or nsRefPtr, depending
# on whether we're nullable.
returnCode = "return ${declName}.forget();"
elif type.nullable():
# Decl is a raw pointer
returnCode = ("NS_IF_ADDREF(${declName});\n"
"return dont_AddRef(${declName});")
else:
# Decl is a non-null raw pointer.
returnCode = ("NS_ADDREF(${declName});\n"
"return dont_AddRef(${declName});")
return result.define(), "nullptr", returnCode
# Since we always force an owning type for callback return values,
# our ${declName} is an OwningNonNull or nsRefPtr. So we can just
# .forget() to get our already_AddRefed.
return result.define(), "nullptr", "return ${declName}.forget();"
if type.isCallback():
return ("already_AddRefed<%s>" % type.unroll().identifier.name,
"nullptr", "return ${declName}.forget();")
@ -8459,12 +8485,12 @@ class CGNativeMember(ClassMethod):
args.insert(0, Argument("JS::Value", "aThisVal"))
# And jscontext bits.
if needCx(returnType, argList, self.extendedAttrs,
self.descriptor, self.passCxAsNeeded):
self.descriptorProvider, self.passCxAsNeeded):
args.insert(0, Argument("JSContext*", "cx"))
# And if we're static, a global
if self.member.isStatic():
globalObjectType = "GlobalObject"
if self.descriptor.workers:
if self.descriptorProvider.workers:
globalObjectType = "Worker" + globalObjectType
args.insert(0, Argument("const %s&" % globalObjectType, "global"))
return args
@ -8514,7 +8540,7 @@ class CGNativeMember(ClassMethod):
else:
typeDecl = "%s&"
return ((typeDecl %
self.descriptor.getDescriptor(iface.identifier.name).nativeType),
self.descriptorProvider.getDescriptor(iface.identifier.name).nativeType),
False, False)
if type.isSpiderMonkeyInterface():
@ -8575,8 +8601,8 @@ class CGNativeMember(ClassMethod):
return declType, False, False
if type.isDictionary():
typeName = CGDictionary.makeDictionaryName(type.inner,
self.descriptor.workers)
typeName = CGDictionary.makeDictionaryName(
type.inner, self.descriptorProvider.workers)
return typeName, True, True
if type.isDate():
@ -8914,6 +8940,7 @@ class CGJSImplMethod(CGNativeMember):
variadicIsSequence=True,
passCxAsNeeded=False)
self.signature = signature
self.descriptor = descriptor
if isConstructor:
self.body = self.getConstructorImpl()
else:
@ -9159,6 +9186,10 @@ class CGJSImplClass(CGBindingImplClass):
def getGetParentObjectBody(self):
return "return mParent;"
def isJSImplementedDescriptor(descriptorProvider):
return (isinstance(descriptorProvider, Descriptor) and
descriptorProvider.interface.isJSImplemented())
class CGCallback(CGClass):
def __init__(self, idlObject, descriptorProvider, baseName, methods,
getters=[], setters=[]):
@ -9167,7 +9198,7 @@ class CGCallback(CGClass):
name = idlObject.identifier.name
if descriptorProvider.workers:
name += "Workers"
if isinstance(descriptorProvider, Descriptor) and descriptorProvider.interface.isJSImplemented():
if isJSImplementedDescriptor(descriptorProvider):
name = jsImplName(name)
# For our public methods that needThisHandling we want most of the
# same args and the same return type as what CallbackMember
@ -9391,11 +9422,15 @@ class CallbackMember(CGNativeMember):
"obj": "nullptr"
}
if isJSImplementedDescriptor(self.descriptorProvider):
isCallbackReturnValue = "JSImpl"
else:
isCallbackReturnValue = "Callback"
convertType = instantiateJSToNativeConversion(
getJSToNativeConversionInfo(self.retvalType,
self.descriptor,
self.descriptorProvider,
exceptionCode=self.exceptionCode,
isCallbackReturnValue=True),
isCallbackReturnValue=isCallbackReturnValue),
replacements)
assignRetval = string.Template(
self.getRetvalInfo(self.retvalType,
@ -9443,7 +9478,7 @@ class CallbackMember(CGNativeMember):
prepend = ""
conversion = prepend + wrapForType(
arg.type, self.descriptor,
arg.type, self.descriptorProvider,
{
'result' : result,
'successCode' : "continue;" if arg.variadic else "break;",