michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=99: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* Wrapper object for reflecting native xpcom objects into JavaScript. */ michael@0: michael@0: #include "xpcprivate.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: #include "XPCLog.h" michael@0: #include "jsprf.h" michael@0: #include "AccessCheck.h" michael@0: #include "WrapperFactory.h" michael@0: #include "XrayWrapper.h" michael@0: michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: michael@0: #include michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include michael@0: michael@0: using namespace xpc; michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace JS; michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(XPCWrappedNative) michael@0: michael@0: // No need to unlink the JS objects: if the XPCWrappedNative is cycle michael@0: // collected then its mFlatJSObject will be cycle collected too and michael@0: // finalization of the mFlatJSObject will unlink the JS objects (see michael@0: // XPC_WN_NoHelper_Finalize and FlatJSObjectFinalized). michael@0: NS_IMETHODIMP_(void) michael@0: NS_CYCLE_COLLECTION_CLASSNAME(XPCWrappedNative)::Unlink(void *p) michael@0: { michael@0: XPCWrappedNative *tmp = static_cast(p); michael@0: tmp->ExpireWrapper(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: NS_CYCLE_COLLECTION_CLASSNAME(XPCWrappedNative)::Traverse michael@0: (void *p, nsCycleCollectionTraversalCallback &cb) michael@0: { michael@0: XPCWrappedNative *tmp = static_cast(p); michael@0: if (!tmp->IsValid()) michael@0: return NS_OK; michael@0: michael@0: if (MOZ_UNLIKELY(cb.WantDebugInfo())) { michael@0: char name[72]; michael@0: XPCNativeScriptableInfo* si = tmp->GetScriptableInfo(); michael@0: if (si) michael@0: JS_snprintf(name, sizeof(name), "XPCWrappedNative (%s)", michael@0: si->GetJSClass()->name); michael@0: else michael@0: JS_snprintf(name, sizeof(name), "XPCWrappedNative"); michael@0: michael@0: cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); michael@0: } else { michael@0: NS_IMPL_CYCLE_COLLECTION_DESCRIBE(XPCWrappedNative, tmp->mRefCnt.get()) michael@0: } michael@0: michael@0: if (tmp->mRefCnt.get() > 1) { michael@0: michael@0: // If our refcount is > 1, our reference to the flat JS object is michael@0: // considered "strong", and we're going to traverse it. michael@0: // michael@0: // If our refcount is <= 1, our reference to the flat JS object is michael@0: // considered "weak", and we're *not* going to traverse it. michael@0: // michael@0: // This reasoning is in line with the slightly confusing lifecycle rules michael@0: // for XPCWrappedNatives, described in a larger comment below and also michael@0: // on our wiki at http://wiki.mozilla.org/XPConnect_object_wrapping michael@0: michael@0: JSObject *obj = tmp->GetFlatJSObjectPreserveColor(); michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFlatJSObject"); michael@0: cb.NoteJSChild(obj); michael@0: } michael@0: michael@0: // XPCWrappedNative keeps its native object alive. michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mIdentity"); michael@0: cb.NoteXPCOMChild(tmp->GetIdentityObject()); michael@0: michael@0: tmp->NoteTearoffs(cb); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: XPCWrappedNative::NoteTearoffs(nsCycleCollectionTraversalCallback& cb) michael@0: { michael@0: // Tearoffs hold their native object alive. If their JS object hasn't been michael@0: // finalized yet we'll note the edge between the JS object and the native michael@0: // (see nsXPConnect::Traverse), but if their JS object has been finalized michael@0: // then the tearoff is only reachable through the XPCWrappedNative, so we michael@0: // record an edge here. michael@0: XPCWrappedNativeTearOffChunk* chunk; michael@0: for (chunk = &mFirstChunk; chunk; chunk = chunk->mNextChunk) { michael@0: XPCWrappedNativeTearOff* to = chunk->mTearOffs; michael@0: for (int i = XPC_WRAPPED_NATIVE_TEAROFFS_PER_CHUNK-1; i >= 0; i--, to++) { michael@0: JSObject* jso = to->GetJSObjectPreserveColor(); michael@0: if (!jso) { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "tearoff's mNative"); michael@0: cb.NoteXPCOMChild(to->GetNative()); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: #ifdef XPC_CHECK_CLASSINFO_CLAIMS michael@0: static void DEBUG_CheckClassInfoClaims(XPCWrappedNative* wrapper); michael@0: #else michael@0: #define DEBUG_CheckClassInfoClaims(wrapper) ((void)0) michael@0: #endif michael@0: michael@0: /***************************************************************************/ michael@0: static nsresult michael@0: FinishCreate(XPCWrappedNativeScope* Scope, michael@0: XPCNativeInterface* Interface, michael@0: nsWrapperCache *cache, michael@0: XPCWrappedNative* inWrapper, michael@0: XPCWrappedNative** resultWrapper); michael@0: michael@0: // static michael@0: // michael@0: // This method handles the special case of wrapping a new global object. michael@0: // michael@0: // The normal code path for wrapping natives goes through michael@0: // XPCConvert::NativeInterface2JSObject, XPCWrappedNative::GetNewOrUsed, michael@0: // and finally into XPCWrappedNative::Init. Unfortunately, this path assumes michael@0: // very early on that we have an XPCWrappedNativeScope and corresponding global michael@0: // JS object, which are the very things we need to create here. So we special- michael@0: // case the logic and do some things in a different order. michael@0: nsresult michael@0: XPCWrappedNative::WrapNewGlobal(xpcObjectHelper &nativeHelper, michael@0: nsIPrincipal *principal, michael@0: bool initStandardClasses, michael@0: JS::CompartmentOptions& aOptions, michael@0: XPCWrappedNative **wrappedGlobal) michael@0: { michael@0: AutoJSContext cx; michael@0: nsISupports *identity = nativeHelper.GetCanonical(); michael@0: michael@0: // The object should specify that it's meant to be global. michael@0: MOZ_ASSERT(nativeHelper.GetScriptableFlags() & nsIXPCScriptable::IS_GLOBAL_OBJECT); michael@0: michael@0: // We shouldn't be reusing globals. michael@0: MOZ_ASSERT(!nativeHelper.GetWrapperCache() || michael@0: !nativeHelper.GetWrapperCache()->GetWrapperPreserveColor()); michael@0: michael@0: // Put together the ScriptableCreateInfo... michael@0: XPCNativeScriptableCreateInfo sciProto; michael@0: XPCNativeScriptableCreateInfo sciMaybe; michael@0: const XPCNativeScriptableCreateInfo& sciWrapper = michael@0: GatherScriptableCreateInfo(identity, nativeHelper.GetClassInfo(), michael@0: sciProto, sciMaybe); michael@0: michael@0: // ...and then ScriptableInfo. We need all this stuff now because it's going michael@0: // to tell us the JSClass of the object we're going to create. michael@0: AutoMarkingNativeScriptableInfoPtr si(cx, XPCNativeScriptableInfo::Construct(&sciWrapper)); michael@0: MOZ_ASSERT(si.get()); michael@0: michael@0: // Finally, we get to the JSClass. michael@0: const JSClass *clasp = si->GetJSClass(); michael@0: MOZ_ASSERT(clasp->flags & JSCLASS_IS_GLOBAL); michael@0: michael@0: // Create the global. michael@0: aOptions.setTrace(XPCWrappedNative::Trace); michael@0: RootedObject global(cx, xpc::CreateGlobalObject(cx, clasp, principal, aOptions)); michael@0: if (!global) michael@0: return NS_ERROR_FAILURE; michael@0: XPCWrappedNativeScope *scope = GetCompartmentPrivate(global)->scope; michael@0: michael@0: // Immediately enter the global's compartment, so that everything else we michael@0: // create ends up there. michael@0: JSAutoCompartment ac(cx, global); michael@0: michael@0: // If requested, initialize the standard classes on the global. michael@0: if (initStandardClasses && ! JS_InitStandardClasses(cx, global)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Make a proto. michael@0: XPCWrappedNativeProto *proto = michael@0: XPCWrappedNativeProto::GetNewOrUsed(scope, michael@0: nativeHelper.GetClassInfo(), &sciProto, michael@0: /* callPostCreatePrototype = */ false); michael@0: if (!proto) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Set up the prototype on the global. michael@0: MOZ_ASSERT(proto->GetJSProtoObject()); michael@0: RootedObject protoObj(cx, proto->GetJSProtoObject()); michael@0: bool success = JS_SplicePrototype(cx, global, protoObj); michael@0: if (!success) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Construct the wrapper, which takes over the strong reference to the michael@0: // native object. michael@0: nsRefPtr wrapper = michael@0: new XPCWrappedNative(nativeHelper.forgetCanonical(), proto); michael@0: michael@0: // michael@0: // We don't call ::Init() on this wrapper, because our setup requirements michael@0: // are different for globals. We do our setup inline here, instead. michael@0: // michael@0: michael@0: // Share mScriptableInfo with the proto. michael@0: // michael@0: // This is probably more trouble than it's worth, since we've already created michael@0: // an XPCNativeScriptableInfo for ourselves. Moreover, most of that class is michael@0: // shared internally via XPCNativeScriptableInfoShared, so the memory michael@0: // savings are negligible. Nevertheless, this is what ::Init() does, and we michael@0: // want to be as consistent as possible with that code. michael@0: XPCNativeScriptableInfo* siProto = proto->GetScriptableInfo(); michael@0: if (siProto && siProto->GetCallback() == sciWrapper.GetCallback()) { michael@0: wrapper->mScriptableInfo = siProto; michael@0: // XPCNativeScriptableShared instances live in a map, and are michael@0: // GCed, but XPCNativeScriptableInfo is per-instance and must be michael@0: // manually managed. If we're switching over to that of the proto, we michael@0: // need to destroy the one we've allocated, and also null out the michael@0: // AutoMarkingPtr, so that it doesn't try to mark garbage data. michael@0: delete si; michael@0: si = nullptr; michael@0: } else { michael@0: wrapper->mScriptableInfo = si; michael@0: } michael@0: michael@0: // Set the JS object to the global we already created. michael@0: wrapper->mFlatJSObject = global; michael@0: wrapper->mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); michael@0: michael@0: // Set the private to the XPCWrappedNative. michael@0: JS_SetPrivate(global, wrapper); michael@0: michael@0: // There are dire comments elsewhere in the code about how a GC can michael@0: // happen somewhere after wrapper initialization but before the wrapper is michael@0: // added to the hashtable in FinishCreate(). It's not clear if that can michael@0: // happen here, but let's just be safe for now. michael@0: AutoMarkingWrappedNativePtr wrapperMarker(cx, wrapper); michael@0: michael@0: // Call the common Init finish routine. This mainly just does an AddRef michael@0: // on behalf of XPConnect (the corresponding Release is in the finalizer michael@0: // hook), but it does some other miscellaneous things too, so we don't michael@0: // inline it. michael@0: success = wrapper->FinishInit(); michael@0: MOZ_ASSERT(success); michael@0: michael@0: // Go through some extra work to find the tearoff. This is kind of silly michael@0: // on a conceptual level: the point of tearoffs is to cache the results michael@0: // of QI-ing mIdentity to different interfaces, and we don't need that michael@0: // since we're dealing with nsISupports. But lots of code expects tearoffs michael@0: // to exist for everything, so we just follow along. michael@0: XPCNativeInterface* iface = XPCNativeInterface::GetNewOrUsed(&NS_GET_IID(nsISupports)); michael@0: MOZ_ASSERT(iface); michael@0: nsresult status; michael@0: success = wrapper->FindTearOff(iface, false, &status); michael@0: if (!success) michael@0: return status; michael@0: michael@0: // Call the common creation finish routine. This does all of the bookkeeping michael@0: // like inserting the wrapper into the wrapper map and setting up the wrapper michael@0: // cache. michael@0: nsresult rv = FinishCreate(scope, iface, nativeHelper.GetWrapperCache(), michael@0: wrapper, wrappedGlobal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: XPCWrappedNative::GetNewOrUsed(xpcObjectHelper& helper, michael@0: XPCWrappedNativeScope* Scope, michael@0: XPCNativeInterface* Interface, michael@0: XPCWrappedNative** resultWrapper) michael@0: { michael@0: MOZ_ASSERT(Interface); michael@0: AutoJSContext cx; michael@0: nsWrapperCache *cache = helper.GetWrapperCache(); michael@0: michael@0: MOZ_ASSERT(!cache || !cache->GetWrapperPreserveColor(), michael@0: "We assume the caller already checked if it could get the " michael@0: "wrapper from the cache."); michael@0: michael@0: nsresult rv; michael@0: michael@0: MOZ_ASSERT(!Scope->GetRuntime()->GCIsRunning(), michael@0: "XPCWrappedNative::GetNewOrUsed called during GC"); michael@0: michael@0: nsISupports *identity = helper.GetCanonical(); michael@0: michael@0: if (!identity) { michael@0: NS_ERROR("This XPCOM object fails in QueryInterface to nsISupports!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsRefPtr wrapper; michael@0: michael@0: Native2WrappedNativeMap* map = Scope->GetWrappedNativeMap(); michael@0: // Some things are nsWrapperCache subclasses but never use the cache, so go michael@0: // ahead and check our map even if we have a cache and it has no existing michael@0: // wrapper: we might have an XPCWrappedNative anyway. michael@0: wrapper = map->Find(identity); michael@0: michael@0: if (wrapper) { michael@0: if (!wrapper->FindTearOff(Interface, false, &rv)) { michael@0: MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); michael@0: return rv; michael@0: } michael@0: wrapper.forget(resultWrapper); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // There is a chance that the object wants to have the self-same JSObject michael@0: // reflection regardless of the scope into which we are reflecting it. michael@0: // Many DOM objects require this. The scriptable helper specifies this michael@0: // in preCreate by indicating a 'parent' of a particular scope. michael@0: // michael@0: // To handle this we need to get the scriptable helper early and ask it. michael@0: // It is possible that we will then end up forwarding this entire call michael@0: // to this same function but with a different scope. michael@0: michael@0: // If we are making a wrapper for the nsIClassInfo interface then michael@0: // We *don't* want to have it use the prototype meant for instances michael@0: // of that class. michael@0: bool iidIsClassInfo = Interface->GetIID()->Equals(NS_GET_IID(nsIClassInfo)); michael@0: uint32_t classInfoFlags; michael@0: bool isClassInfoSingleton = helper.GetClassInfo() == helper.Object() && michael@0: NS_SUCCEEDED(helper.GetClassInfo() michael@0: ->GetFlags(&classInfoFlags)) && michael@0: (classInfoFlags & nsIClassInfo::SINGLETON_CLASSINFO); michael@0: bool isClassInfo = iidIsClassInfo || isClassInfoSingleton; michael@0: michael@0: nsIClassInfo *info = helper.GetClassInfo(); michael@0: michael@0: XPCNativeScriptableCreateInfo sciProto; michael@0: XPCNativeScriptableCreateInfo sci; michael@0: michael@0: // Gather scriptable create info if we are wrapping something michael@0: // other than an nsIClassInfo object. We need to not do this for michael@0: // nsIClassInfo objects because often nsIClassInfo implementations michael@0: // are also nsIXPCScriptable helper implementations, but the helper michael@0: // code is obviously intended for the implementation of the class michael@0: // described by the nsIClassInfo, not for the class info object michael@0: // itself. michael@0: const XPCNativeScriptableCreateInfo& sciWrapper = michael@0: isClassInfo ? sci : michael@0: GatherScriptableCreateInfo(identity, info, sciProto, sci); michael@0: michael@0: RootedObject parent(cx, Scope->GetGlobalJSObject()); michael@0: michael@0: RootedValue newParentVal(cx, NullValue()); michael@0: michael@0: mozilla::Maybe ac; michael@0: michael@0: if (sciWrapper.GetFlags().WantPreCreate()) { michael@0: // PreCreate may touch dead compartments. michael@0: js::AutoMaybeTouchDeadZones agc(parent); michael@0: michael@0: RootedObject plannedParent(cx, parent); michael@0: nsresult rv = sciWrapper.GetCallback()->PreCreate(identity, cx, michael@0: parent, parent.address()); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: rv = NS_OK; michael@0: michael@0: MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(parent), michael@0: "Xray wrapper being used to parent XPCWrappedNative?"); michael@0: michael@0: ac.construct(static_cast(cx), parent); michael@0: michael@0: if (parent != plannedParent) { michael@0: XPCWrappedNativeScope* betterScope = GetObjectScope(parent); michael@0: if (betterScope != Scope) michael@0: return GetNewOrUsed(helper, betterScope, Interface, resultWrapper); michael@0: michael@0: newParentVal = OBJECT_TO_JSVAL(parent); michael@0: } michael@0: michael@0: // Take the performance hit of checking the hashtable again in case michael@0: // the preCreate call caused the wrapper to get created through some michael@0: // interesting path (the DOM code tends to make this happen sometimes). michael@0: michael@0: if (cache) { michael@0: RootedObject cached(cx, cache->GetWrapper()); michael@0: if (cached) michael@0: wrapper = XPCWrappedNative::Get(cached); michael@0: } else { michael@0: wrapper = map->Find(identity); michael@0: } michael@0: michael@0: if (wrapper) { michael@0: if (wrapper->FindTearOff(Interface, false, &rv)) { michael@0: MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); michael@0: return rv; michael@0: } michael@0: wrapper.forget(resultWrapper); michael@0: return NS_OK; michael@0: } michael@0: } else { michael@0: ac.construct(static_cast(cx), parent); michael@0: } michael@0: michael@0: AutoMarkingWrappedNativeProtoPtr proto(cx); michael@0: michael@0: // If there is ClassInfo (and we are not building a wrapper for the michael@0: // nsIClassInfo interface) then we use a wrapper that needs a prototype. michael@0: michael@0: // Note that the security check happens inside FindTearOff - after the michael@0: // wrapper is actually created, but before JS code can see it. michael@0: michael@0: if (info && !isClassInfo) { michael@0: proto = XPCWrappedNativeProto::GetNewOrUsed(Scope, info, &sciProto); michael@0: if (!proto) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: wrapper = new XPCWrappedNative(helper.forgetCanonical(), proto); michael@0: } else { michael@0: AutoMarkingNativeInterfacePtr iface(cx, Interface); michael@0: if (!iface) michael@0: iface = XPCNativeInterface::GetISupports(); michael@0: michael@0: AutoMarkingNativeSetPtr set(cx); michael@0: set = XPCNativeSet::GetNewOrUsed(nullptr, iface, 0); michael@0: michael@0: if (!set) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: wrapper = michael@0: new XPCWrappedNative(helper.forgetCanonical(), Scope, set); michael@0: } michael@0: michael@0: MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(parent), michael@0: "Xray wrapper being used to parent XPCWrappedNative?"); michael@0: michael@0: // We use an AutoMarkingPtr here because it is possible for JS gc to happen michael@0: // after we have Init'd the wrapper but *before* we add it to the hashtable. michael@0: // This would cause the mSet to get collected and we'd later crash. I've michael@0: // *seen* this happen. michael@0: AutoMarkingWrappedNativePtr wrapperMarker(cx, wrapper); michael@0: michael@0: if (!wrapper->Init(parent, &sciWrapper)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (!wrapper->FindTearOff(Interface, false, &rv)) { michael@0: MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); michael@0: return rv; michael@0: } michael@0: michael@0: return FinishCreate(Scope, Interface, cache, wrapper, resultWrapper); michael@0: } michael@0: michael@0: static nsresult michael@0: FinishCreate(XPCWrappedNativeScope* Scope, michael@0: XPCNativeInterface* Interface, michael@0: nsWrapperCache *cache, michael@0: XPCWrappedNative* inWrapper, michael@0: XPCWrappedNative** resultWrapper) michael@0: { michael@0: AutoJSContext cx; michael@0: MOZ_ASSERT(inWrapper); michael@0: michael@0: Native2WrappedNativeMap* map = Scope->GetWrappedNativeMap(); michael@0: michael@0: nsRefPtr wrapper; michael@0: // Deal with the case where the wrapper got created as a side effect michael@0: // of one of our calls out of this code. Add() returns the (possibly michael@0: // pre-existing) wrapper that ultimately ends up in the map, which is michael@0: // what we want. michael@0: wrapper = map->Add(inWrapper); michael@0: if (!wrapper) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (wrapper == inWrapper) { michael@0: JSObject *flat = wrapper->GetFlatJSObject(); michael@0: MOZ_ASSERT(!cache || !cache->GetWrapperPreserveColor() || michael@0: flat == cache->GetWrapperPreserveColor(), michael@0: "This object has a cached wrapper that's different from " michael@0: "the JSObject held by its native wrapper?"); michael@0: michael@0: if (cache && !cache->GetWrapperPreserveColor()) michael@0: cache->SetWrapper(flat); michael@0: michael@0: // Our newly created wrapper is the one that we just added to the table. michael@0: // All is well. Call PostCreate as necessary. michael@0: XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo(); michael@0: if (si && si->GetFlags().WantPostCreate()) { michael@0: nsresult rv = si->GetCallback()->PostCreate(wrapper, cx, flat); michael@0: if (NS_FAILED(rv)) { michael@0: // PostCreate failed and that's Very Bad. We'll remove it from michael@0: // the map and mark it as invalid, but the PostCreate function michael@0: // may have handed the partially-constructed-and-now-invalid michael@0: // wrapper to someone before failing. Or, perhaps worse, the michael@0: // PostCreate call could have triggered code that reentered michael@0: // XPConnect and tried to wrap the same object. In that case michael@0: // *we* hand out the invalid wrapper since it is already in our michael@0: // map :( michael@0: NS_ERROR("PostCreate failed! This is known to cause " michael@0: "inconsistent state for some class types and may even " michael@0: "cause a crash in combination with a JS GC. Fix the " michael@0: "failing PostCreate ASAP!"); michael@0: michael@0: map->Remove(wrapper); michael@0: michael@0: // This would be a good place to tell the wrapper not to remove michael@0: // itself from the map when it dies... See bug 429442. michael@0: michael@0: if (cache) michael@0: cache->ClearWrapper(); michael@0: wrapper->Release(); michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: DEBUG_CheckClassInfoClaims(wrapper); michael@0: wrapper.forget(resultWrapper); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: XPCWrappedNative::GetUsedOnly(nsISupports* Object, michael@0: XPCWrappedNativeScope* Scope, michael@0: XPCNativeInterface* Interface, michael@0: XPCWrappedNative** resultWrapper) michael@0: { michael@0: AutoJSContext cx; michael@0: MOZ_ASSERT(Object, "XPCWrappedNative::GetUsedOnly was called with a null Object"); michael@0: MOZ_ASSERT(Interface); michael@0: michael@0: nsRefPtr wrapper; michael@0: nsWrapperCache* cache = nullptr; michael@0: CallQueryInterface(Object, &cache); michael@0: if (cache) { michael@0: RootedObject flat(cx, cache->GetWrapper()); michael@0: if (!flat) { michael@0: *resultWrapper = nullptr; michael@0: return NS_OK; michael@0: } michael@0: wrapper = XPCWrappedNative::Get(flat); michael@0: } else { michael@0: nsCOMPtr identity = do_QueryInterface(Object); michael@0: michael@0: if (!identity) { michael@0: NS_ERROR("This XPCOM object fails in QueryInterface to nsISupports!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: Native2WrappedNativeMap* map = Scope->GetWrappedNativeMap(); michael@0: michael@0: wrapper = map->Find(identity); michael@0: if (!wrapper) { michael@0: *resultWrapper = nullptr; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: nsresult rv; michael@0: if (!wrapper->FindTearOff(Interface, false, &rv)) { michael@0: MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); michael@0: return rv; michael@0: } michael@0: michael@0: wrapper.forget(resultWrapper); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This ctor is used if this object will have a proto. michael@0: XPCWrappedNative::XPCWrappedNative(already_AddRefed&& aIdentity, michael@0: XPCWrappedNativeProto* aProto) michael@0: : mMaybeProto(aProto), michael@0: mSet(aProto->GetSet()), michael@0: mScriptableInfo(nullptr) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mIdentity = aIdentity.take(); michael@0: mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); michael@0: michael@0: MOZ_ASSERT(mMaybeProto, "bad ctor param"); michael@0: MOZ_ASSERT(mSet, "bad ctor param"); michael@0: } michael@0: michael@0: // This ctor is used if this object will NOT have a proto. michael@0: XPCWrappedNative::XPCWrappedNative(already_AddRefed&& aIdentity, michael@0: XPCWrappedNativeScope* aScope, michael@0: XPCNativeSet* aSet) michael@0: michael@0: : mMaybeScope(TagScope(aScope)), michael@0: mSet(aSet), michael@0: mScriptableInfo(nullptr) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mIdentity = aIdentity.take(); michael@0: mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); michael@0: michael@0: MOZ_ASSERT(aScope, "bad ctor param"); michael@0: MOZ_ASSERT(aSet, "bad ctor param"); michael@0: } michael@0: michael@0: XPCWrappedNative::~XPCWrappedNative() michael@0: { michael@0: Destroy(); michael@0: } michael@0: michael@0: void michael@0: XPCWrappedNative::Destroy() michael@0: { michael@0: XPCWrappedNativeProto* proto = GetProto(); michael@0: michael@0: if (mScriptableInfo && michael@0: (!HasProto() || michael@0: (proto && proto->GetScriptableInfo() != mScriptableInfo))) { michael@0: delete mScriptableInfo; michael@0: mScriptableInfo = nullptr; michael@0: } michael@0: michael@0: XPCWrappedNativeScope *scope = GetScope(); michael@0: if (scope) { michael@0: Native2WrappedNativeMap* map = scope->GetWrappedNativeMap(); michael@0: michael@0: // Post-1.9 we should not remove this wrapper from the map if it is michael@0: // uninitialized. michael@0: map->Remove(this); michael@0: } michael@0: michael@0: if (mIdentity) { michael@0: XPCJSRuntime* rt = GetRuntime(); michael@0: if (rt && rt->GetDoingFinalization()) { michael@0: nsContentUtils::DeferredFinalize(mIdentity); michael@0: mIdentity = nullptr; michael@0: } else { michael@0: NS_RELEASE(mIdentity); michael@0: } michael@0: } michael@0: michael@0: mMaybeScope = nullptr; michael@0: } michael@0: michael@0: void michael@0: XPCWrappedNative::UpdateScriptableInfo(XPCNativeScriptableInfo *si) michael@0: { michael@0: MOZ_ASSERT(mScriptableInfo, "UpdateScriptableInfo expects an existing scriptable info"); michael@0: michael@0: // Write barrier for incremental GC. michael@0: JSRuntime* rt = GetRuntime()->Runtime(); michael@0: if (IsIncrementalBarrierNeeded(rt)) michael@0: mScriptableInfo->Mark(); michael@0: michael@0: mScriptableInfo = si; michael@0: } michael@0: michael@0: void michael@0: XPCWrappedNative::SetProto(XPCWrappedNativeProto* p) michael@0: { michael@0: MOZ_ASSERT(!IsWrapperExpired(), "bad ptr!"); michael@0: michael@0: MOZ_ASSERT(HasProto()); michael@0: michael@0: // Write barrier for incremental GC. michael@0: JSRuntime* rt = GetRuntime()->Runtime(); michael@0: GetProto()->WriteBarrierPre(rt); michael@0: michael@0: mMaybeProto = p; michael@0: } michael@0: michael@0: // This is factored out so that it can be called publicly michael@0: // static michael@0: void michael@0: XPCWrappedNative::GatherProtoScriptableCreateInfo(nsIClassInfo* classInfo, michael@0: XPCNativeScriptableCreateInfo& sciProto) michael@0: { michael@0: MOZ_ASSERT(classInfo, "bad param"); michael@0: MOZ_ASSERT(!sciProto.GetCallback(), "bad param"); michael@0: michael@0: nsXPCClassInfo *classInfoHelper = nullptr; michael@0: CallQueryInterface(classInfo, &classInfoHelper); michael@0: if (classInfoHelper) { michael@0: nsCOMPtr helper = michael@0: dont_AddRef(static_cast(classInfoHelper)); michael@0: uint32_t flags = classInfoHelper->GetScriptableFlags(); michael@0: sciProto.SetCallback(helper.forget()); michael@0: sciProto.SetFlags(flags); michael@0: sciProto.SetInterfacesBitmap(classInfoHelper->GetInterfacesBitmap()); michael@0: michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr possibleHelper; michael@0: nsresult rv = classInfo->GetHelperForLanguage(nsIProgrammingLanguage::JAVASCRIPT, michael@0: getter_AddRefs(possibleHelper)); michael@0: if (NS_SUCCEEDED(rv) && possibleHelper) { michael@0: nsCOMPtr helper(do_QueryInterface(possibleHelper)); michael@0: if (helper) { michael@0: uint32_t flags = helper->GetScriptableFlags(); michael@0: sciProto.SetCallback(helper.forget()); michael@0: sciProto.SetFlags(flags); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // static michael@0: const XPCNativeScriptableCreateInfo& michael@0: XPCWrappedNative::GatherScriptableCreateInfo(nsISupports* obj, michael@0: nsIClassInfo* classInfo, michael@0: XPCNativeScriptableCreateInfo& sciProto, michael@0: XPCNativeScriptableCreateInfo& sciWrapper) michael@0: { michael@0: MOZ_ASSERT(!sciWrapper.GetCallback(), "bad param"); michael@0: michael@0: // Get the class scriptable helper (if present) michael@0: if (classInfo) { michael@0: GatherProtoScriptableCreateInfo(classInfo, sciProto); michael@0: michael@0: if (sciProto.GetFlags().DontAskInstanceForScriptable()) michael@0: return sciProto; michael@0: } michael@0: michael@0: // Do the same for the wrapper specific scriptable michael@0: nsCOMPtr helper(do_QueryInterface(obj)); michael@0: if (helper) { michael@0: uint32_t flags = helper->GetScriptableFlags(); michael@0: sciWrapper.SetCallback(helper.forget()); michael@0: sciWrapper.SetFlags(flags); michael@0: michael@0: // A whole series of assertions to catch bad uses of scriptable flags on michael@0: // the siWrapper... michael@0: michael@0: MOZ_ASSERT(!(sciWrapper.GetFlags().WantPreCreate() && michael@0: !sciProto.GetFlags().WantPreCreate()), michael@0: "Can't set WANT_PRECREATE on an instance scriptable " michael@0: "without also setting it on the class scriptable"); michael@0: michael@0: MOZ_ASSERT(!(sciWrapper.GetFlags().DontEnumStaticProps() && michael@0: !sciProto.GetFlags().DontEnumStaticProps() && michael@0: sciProto.GetCallback()), michael@0: "Can't set DONT_ENUM_STATIC_PROPS on an instance scriptable " michael@0: "without also setting it on the class scriptable (if present and shared)"); michael@0: michael@0: MOZ_ASSERT(!(sciWrapper.GetFlags().DontEnumQueryInterface() && michael@0: !sciProto.GetFlags().DontEnumQueryInterface() && michael@0: sciProto.GetCallback()), michael@0: "Can't set DONT_ENUM_QUERY_INTERFACE on an instance scriptable " michael@0: "without also setting it on the class scriptable (if present and shared)"); michael@0: michael@0: MOZ_ASSERT(!(sciWrapper.GetFlags().DontAskInstanceForScriptable() && michael@0: !sciProto.GetFlags().DontAskInstanceForScriptable()), michael@0: "Can't set DONT_ASK_INSTANCE_FOR_SCRIPTABLE on an instance scriptable " michael@0: "without also setting it on the class scriptable"); michael@0: michael@0: MOZ_ASSERT(!(sciWrapper.GetFlags().ClassInfoInterfacesOnly() && michael@0: !sciProto.GetFlags().ClassInfoInterfacesOnly() && michael@0: sciProto.GetCallback()), michael@0: "Can't set CLASSINFO_INTERFACES_ONLY on an instance scriptable " michael@0: "without also setting it on the class scriptable (if present and shared)"); michael@0: michael@0: MOZ_ASSERT(!(sciWrapper.GetFlags().AllowPropModsDuringResolve() && michael@0: !sciProto.GetFlags().AllowPropModsDuringResolve() && michael@0: sciProto.GetCallback()), michael@0: "Can't set ALLOW_PROP_MODS_DURING_RESOLVE on an instance scriptable " michael@0: "without also setting it on the class scriptable (if present and shared)"); michael@0: michael@0: MOZ_ASSERT(!(sciWrapper.GetFlags().AllowPropModsToPrototype() && michael@0: !sciProto.GetFlags().AllowPropModsToPrototype() && michael@0: sciProto.GetCallback()), michael@0: "Can't set ALLOW_PROP_MODS_TO_PROTOTYPE on an instance scriptable " michael@0: "without also setting it on the class scriptable (if present and shared)"); michael@0: michael@0: return sciWrapper; michael@0: } michael@0: michael@0: return sciProto; michael@0: } michael@0: michael@0: bool michael@0: XPCWrappedNative::Init(HandleObject parent, michael@0: const XPCNativeScriptableCreateInfo* sci) michael@0: { michael@0: AutoJSContext cx; michael@0: // setup our scriptable info... michael@0: michael@0: if (sci->GetCallback()) { michael@0: if (HasProto()) { michael@0: XPCNativeScriptableInfo* siProto = GetProto()->GetScriptableInfo(); michael@0: if (siProto && siProto->GetCallback() == sci->GetCallback()) michael@0: mScriptableInfo = siProto; michael@0: } michael@0: if (!mScriptableInfo) { michael@0: mScriptableInfo = michael@0: XPCNativeScriptableInfo::Construct(sci); michael@0: michael@0: if (!mScriptableInfo) michael@0: return false; michael@0: } michael@0: } michael@0: XPCNativeScriptableInfo* si = mScriptableInfo; michael@0: michael@0: // create our flatJSObject michael@0: michael@0: const JSClass* jsclazz = si ? si->GetJSClass() : Jsvalify(&XPC_WN_NoHelper_JSClass.base); michael@0: michael@0: // We should have the global jsclass flag if and only if we're a global. michael@0: MOZ_ASSERT_IF(si, !!si->GetFlags().IsGlobalObject() == !!(jsclazz->flags & JSCLASS_IS_GLOBAL)); michael@0: michael@0: MOZ_ASSERT(jsclazz && michael@0: jsclazz->name && michael@0: jsclazz->flags && michael@0: jsclazz->addProperty && michael@0: jsclazz->delProperty && michael@0: jsclazz->getProperty && michael@0: jsclazz->setProperty && michael@0: jsclazz->enumerate && michael@0: jsclazz->resolve && michael@0: jsclazz->convert && michael@0: jsclazz->finalize, "bad class"); michael@0: michael@0: RootedObject protoJSObject(cx, HasProto() ? michael@0: GetProto()->GetJSProtoObject() : michael@0: JS_GetObjectPrototype(cx, parent)); michael@0: if (!protoJSObject) { michael@0: return false; michael@0: } michael@0: michael@0: mFlatJSObject = JS_NewObject(cx, jsclazz, protoJSObject, parent); michael@0: if (!mFlatJSObject) { michael@0: mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID); michael@0: return false; michael@0: } michael@0: michael@0: mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); michael@0: JS_SetPrivate(mFlatJSObject, this); michael@0: michael@0: return FinishInit(); michael@0: } michael@0: michael@0: bool michael@0: XPCWrappedNative::FinishInit() michael@0: { michael@0: AutoJSContext cx; michael@0: michael@0: // This reference will be released when mFlatJSObject is finalized. michael@0: // Since this reference will push the refcount to 2 it will also root michael@0: // mFlatJSObject; michael@0: MOZ_ASSERT(1 == mRefCnt, "unexpected refcount value"); michael@0: NS_ADDREF(this); michael@0: michael@0: if (mScriptableInfo && mScriptableInfo->GetFlags().WantCreate() && michael@0: NS_FAILED(mScriptableInfo->GetCallback()->Create(this, cx, michael@0: mFlatJSObject))) { michael@0: return false; michael@0: } michael@0: michael@0: // A hack for bug 517665, increase the probability for GC. michael@0: JS_updateMallocCounter(cx, 2 * sizeof(XPCWrappedNative)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XPCWrappedNative) michael@0: NS_INTERFACE_MAP_ENTRY(nsIXPConnectWrappedNative) michael@0: NS_INTERFACE_MAP_ENTRY(nsIXPConnectJSObjectHolder) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPConnectWrappedNative) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(XPCWrappedNative) michael@0: michael@0: // Release calls Destroy() immediately when the refcount drops to 0 to michael@0: // clear the weak references nsXPConnect has to XPCWNs and to ensure there michael@0: // are no pointers to dying protos. michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(XPCWrappedNative, Destroy()) michael@0: michael@0: /* michael@0: * Wrapped Native lifetime management is messy! michael@0: * michael@0: * - At creation we push the refcount to 2 (only one of which is owned by michael@0: * the native caller that caused the wrapper creation). michael@0: * - During the JS GC Mark phase we mark any wrapper with a refcount > 1. michael@0: * - The *only* thing that can make the wrapper get destroyed is the michael@0: * finalization of mFlatJSObject. And *that* should only happen if the only michael@0: * reference is the single extra (internal) reference we hold. michael@0: * michael@0: * - The wrapper has a pointer to the nsISupports 'view' of the wrapped native michael@0: * object i.e... mIdentity. This is held until the wrapper's refcount goes michael@0: * to zero and the wrapper is released, or until an expired wrapper (i.e., michael@0: * one unlinked by the cycle collector) has had its JS object finalized. michael@0: * michael@0: * - The wrapper also has 'tearoffs'. It has one tearoff for each interface michael@0: * that is actually used on the native object. 'Used' means we have either michael@0: * needed to QueryInterface to verify the availability of that interface michael@0: * of that we've had to QueryInterface in order to actually make a call michael@0: * into the wrapped object via the pointer for the given interface. michael@0: * michael@0: * - Each tearoff's 'mNative' member (if non-null) indicates one reference michael@0: * held by our wrapper on the wrapped native for the given interface michael@0: * associated with the tearoff. If we release that reference then we set michael@0: * the tearoff's 'mNative' to null. michael@0: * michael@0: * - We use the occasion of the JavaScript GCCallback for the JSGC_MARK_END michael@0: * event to scan the tearoffs of all wrappers for non-null mNative members michael@0: * that represent unused references. We can tell that a given tearoff's michael@0: * mNative is unused by noting that no live XPCCallContexts hold a pointer michael@0: * to the tearoff. michael@0: * michael@0: * - As a time/space tradeoff we may decide to not do this scanning on michael@0: * *every* JavaScript GC. We *do* want to do this *sometimes* because michael@0: * we want to allow for wrapped native's to do their own tearoff patterns. michael@0: * So, we want to avoid holding references to interfaces that we don't need. michael@0: * At the same time, we don't want to be bracketing every call into a michael@0: * wrapped native object with a QueryInterface/Release pair. And we *never* michael@0: * make a call into the object except via the correct interface for which michael@0: * we've QI'd. michael@0: * michael@0: * - Each tearoff *can* have a mJSObject whose lazily resolved properties michael@0: * represent the methods/attributes/constants of that specific interface. michael@0: * This is optionally reflected into JavaScript as "foo.nsIFoo" when "foo" michael@0: * is the name of mFlatJSObject and "nsIFoo" is the name of the given michael@0: * interface associated with the tearoff. When we create the tearoff's michael@0: * mJSObject we set it's parent to be mFlatJSObject. This way we know that michael@0: * when mFlatJSObject get's collected there are no outstanding reachable michael@0: * tearoff mJSObjects. Note that we must clear the private of any lingering michael@0: * mJSObjects at this point because we have no guarentee of the *order* of michael@0: * finalization within a given gc cycle. michael@0: */ michael@0: michael@0: void michael@0: XPCWrappedNative::FlatJSObjectFinalized() michael@0: { michael@0: if (!IsValid()) michael@0: return; michael@0: michael@0: // Iterate the tearoffs and null out each of their JSObject's privates. michael@0: // This will keep them from trying to access their pointers to the michael@0: // dying tearoff object. We can safely assume that those remaining michael@0: // JSObjects are about to be finalized too. michael@0: michael@0: XPCWrappedNativeTearOffChunk* chunk; michael@0: for (chunk = &mFirstChunk; chunk; chunk = chunk->mNextChunk) { michael@0: XPCWrappedNativeTearOff* to = chunk->mTearOffs; michael@0: for (int i = XPC_WRAPPED_NATIVE_TEAROFFS_PER_CHUNK-1; i >= 0; i--, to++) { michael@0: JSObject* jso = to->GetJSObjectPreserveColor(); michael@0: if (jso) { michael@0: MOZ_ASSERT(JS_IsAboutToBeFinalizedUnbarriered(&jso)); michael@0: JS_SetPrivate(jso, nullptr); michael@0: to->JSObjectFinalized(); michael@0: } michael@0: michael@0: // We also need to release any native pointers held... michael@0: nsISupports* obj = to->GetNative(); michael@0: if (obj) { michael@0: #ifdef XP_WIN michael@0: // Try to detect free'd pointer michael@0: MOZ_ASSERT(*(int*)obj != 0xdddddddd, "bad pointer!"); michael@0: MOZ_ASSERT(*(int*)obj != 0, "bad pointer!"); michael@0: #endif michael@0: XPCJSRuntime* rt = GetRuntime(); michael@0: if (rt) { michael@0: nsContentUtils::DeferredFinalize(obj); michael@0: } else { michael@0: obj->Release(); michael@0: } michael@0: to->SetNative(nullptr); michael@0: } michael@0: michael@0: to->SetInterface(nullptr); michael@0: } michael@0: } michael@0: michael@0: nsWrapperCache *cache = nullptr; michael@0: CallQueryInterface(mIdentity, &cache); michael@0: if (cache) michael@0: cache->ClearWrapper(); michael@0: michael@0: mFlatJSObject = nullptr; michael@0: mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID); michael@0: michael@0: MOZ_ASSERT(mIdentity, "bad pointer!"); michael@0: #ifdef XP_WIN michael@0: // Try to detect free'd pointer michael@0: MOZ_ASSERT(*(int*)mIdentity != 0xdddddddd, "bad pointer!"); michael@0: MOZ_ASSERT(*(int*)mIdentity != 0, "bad pointer!"); michael@0: #endif michael@0: michael@0: if (IsWrapperExpired()) { michael@0: Destroy(); michael@0: } michael@0: michael@0: // Note that it's not safe to touch mNativeWrapper here since it's michael@0: // likely that it has already been finalized. michael@0: michael@0: Release(); michael@0: } michael@0: michael@0: void michael@0: XPCWrappedNative::SystemIsBeingShutDown() michael@0: { michael@0: if (!IsValid()) michael@0: return; michael@0: michael@0: // The long standing strategy is to leak some objects still held at shutdown. michael@0: // The general problem is that propagating release out of xpconnect at michael@0: // shutdown time causes a world of problems. michael@0: michael@0: // We leak mIdentity (see above). michael@0: michael@0: // short circuit future finalization michael@0: JS_SetPrivate(mFlatJSObject, nullptr); michael@0: mFlatJSObject = nullptr; michael@0: mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID); michael@0: michael@0: XPCWrappedNativeProto* proto = GetProto(); michael@0: michael@0: if (HasProto()) michael@0: proto->SystemIsBeingShutDown(); michael@0: michael@0: if (mScriptableInfo && michael@0: (!HasProto() || michael@0: (proto && proto->GetScriptableInfo() != mScriptableInfo))) { michael@0: delete mScriptableInfo; michael@0: } michael@0: michael@0: // cleanup the tearoffs... michael@0: michael@0: XPCWrappedNativeTearOffChunk* chunk; michael@0: for (chunk = &mFirstChunk; chunk; chunk = chunk->mNextChunk) { michael@0: XPCWrappedNativeTearOff* to = chunk->mTearOffs; michael@0: for (int i = XPC_WRAPPED_NATIVE_TEAROFFS_PER_CHUNK-1; i >= 0; i--, to++) { michael@0: if (JSObject *jso = to->GetJSObjectPreserveColor()) { michael@0: JS_SetPrivate(jso, nullptr); michael@0: to->SetJSObject(nullptr); michael@0: } michael@0: // We leak the tearoff mNative michael@0: // (for the same reason we leak mIdentity - see above). michael@0: to->SetNative(nullptr); michael@0: to->SetInterface(nullptr); michael@0: } michael@0: } michael@0: michael@0: if (mFirstChunk.mNextChunk) { michael@0: delete mFirstChunk.mNextChunk; michael@0: mFirstChunk.mNextChunk = nullptr; michael@0: } michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: // Dynamically ensure that two objects don't end up with the same private. michael@0: class MOZ_STACK_CLASS AutoClonePrivateGuard { michael@0: public: michael@0: AutoClonePrivateGuard(JSContext *cx, JSObject *aOld, JSObject *aNew) michael@0: : mOldReflector(cx, aOld), mNewReflector(cx, aNew) michael@0: { michael@0: MOZ_ASSERT(JS_GetPrivate(aOld) == JS_GetPrivate(aNew)); michael@0: } michael@0: michael@0: ~AutoClonePrivateGuard() michael@0: { michael@0: if (JS_GetPrivate(mOldReflector)) { michael@0: JS_SetPrivate(mNewReflector, nullptr); michael@0: } michael@0: } michael@0: michael@0: private: michael@0: RootedObject mOldReflector; michael@0: RootedObject mNewReflector; michael@0: }; michael@0: michael@0: // static michael@0: nsresult michael@0: XPCWrappedNative::ReparentWrapperIfFound(XPCWrappedNativeScope* aOldScope, michael@0: XPCWrappedNativeScope* aNewScope, michael@0: HandleObject aNewParent, michael@0: nsISupports* aCOMObj) michael@0: { michael@0: // Check if we're near the stack limit before we get anywhere near the michael@0: // transplanting code. michael@0: AutoJSContext cx; michael@0: JS_CHECK_RECURSION(cx, return NS_ERROR_FAILURE); michael@0: michael@0: XPCNativeInterface* iface = XPCNativeInterface::GetISupports(); michael@0: if (!iface) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv; michael@0: michael@0: nsRefPtr wrapper; michael@0: RootedObject flat(cx); michael@0: nsWrapperCache* cache = nullptr; michael@0: CallQueryInterface(aCOMObj, &cache); michael@0: if (cache) { michael@0: flat = cache->GetWrapper(); michael@0: if (flat) { michael@0: wrapper = XPCWrappedNative::Get(flat); michael@0: MOZ_ASSERT(wrapper->GetScope() == aOldScope, michael@0: "Incorrect scope passed"); michael@0: } michael@0: } else { michael@0: rv = XPCWrappedNative::GetUsedOnly(aCOMObj, aOldScope, iface, michael@0: getter_AddRefs(wrapper)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (wrapper) michael@0: flat = wrapper->GetFlatJSObject(); michael@0: } michael@0: michael@0: if (!flat) michael@0: return NS_OK; michael@0: michael@0: JSAutoCompartment ac(cx, aNewScope->GetGlobalJSObject()); michael@0: michael@0: if (aOldScope != aNewScope) { michael@0: // Oh, so now we need to move the wrapper to a different scope. michael@0: AutoMarkingWrappedNativeProtoPtr oldProto(cx); michael@0: AutoMarkingWrappedNativeProtoPtr newProto(cx); michael@0: michael@0: // Cross-scope means cross-compartment. michael@0: MOZ_ASSERT(js::GetObjectCompartment(aOldScope->GetGlobalJSObject()) != michael@0: js::GetObjectCompartment(aNewScope->GetGlobalJSObject())); michael@0: MOZ_ASSERT(aNewParent, "won't be able to find the new parent"); michael@0: michael@0: if (wrapper->HasProto()) { michael@0: oldProto = wrapper->GetProto(); michael@0: XPCNativeScriptableInfo *info = oldProto->GetScriptableInfo(); michael@0: XPCNativeScriptableCreateInfo ci(*info); michael@0: newProto = michael@0: XPCWrappedNativeProto::GetNewOrUsed(aNewScope, michael@0: oldProto->GetClassInfo(), michael@0: &ci); michael@0: if (!newProto) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: // First, the clone of the reflector, get a copy of its michael@0: // properties and clone its expando chain. The only part that is michael@0: // dangerous here if we have to return early is that we must avoid michael@0: // ending up with two reflectors pointing to the same WN. Other than michael@0: // that, the objects we create will just go away if we return early. michael@0: michael@0: RootedObject proto(cx, newProto->GetJSProtoObject()); michael@0: RootedObject newobj(cx, JS_CloneObject(cx, flat, proto, aNewParent)); michael@0: if (!newobj) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // At this point, both |flat| and |newobj| point to the same wrapped michael@0: // native, which is bad, because one of them will end up finalizing michael@0: // a wrapped native it does not own. |cloneGuard| ensures that if we michael@0: // exit before calling clearing |flat|'s private the private of michael@0: // |newobj| will be set to nullptr. |flat| will go away soon, because michael@0: // we swap it with another object during the transplant and let that michael@0: // object die. michael@0: RootedObject propertyHolder(cx); michael@0: { michael@0: AutoClonePrivateGuard cloneGuard(cx, flat, newobj); michael@0: michael@0: propertyHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), michael@0: aNewParent); michael@0: if (!propertyHolder) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: if (!JS_CopyPropertiesFrom(cx, propertyHolder, flat)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Expandos from other compartments are attached to the target JS object. michael@0: // Copy them over, and let the old ones die a natural death. michael@0: if (!XrayUtils::CloneExpandoChain(cx, newobj, flat)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // We've set up |newobj|, so we make it own the WN by nulling out michael@0: // the private of |flat|. michael@0: // michael@0: // NB: It's important to do this _after_ copying the properties to michael@0: // propertyHolder. Otherwise, an object with |foo.x === foo| will michael@0: // crash when JS_CopyPropertiesFrom tries to call wrap() on foo.x. michael@0: JS_SetPrivate(flat, nullptr); michael@0: } michael@0: michael@0: // Update scope maps. This section modifies global state, so from michael@0: // here on out we crash if anything fails. michael@0: Native2WrappedNativeMap* oldMap = aOldScope->GetWrappedNativeMap(); michael@0: Native2WrappedNativeMap* newMap = aNewScope->GetWrappedNativeMap(); michael@0: michael@0: oldMap->Remove(wrapper); michael@0: michael@0: if (wrapper->HasProto()) michael@0: wrapper->SetProto(newProto); michael@0: michael@0: // If the wrapper has no scriptable or it has a non-shared michael@0: // scriptable, then we don't need to mess with it. michael@0: // Otherwise... michael@0: michael@0: if (wrapper->mScriptableInfo && michael@0: wrapper->mScriptableInfo == oldProto->GetScriptableInfo()) { michael@0: // The new proto had better have the same JSClass stuff as michael@0: // the old one! We maintain a runtime wide unique map of michael@0: // this stuff. So, if these don't match then the caller is michael@0: // doing something bad here. michael@0: michael@0: MOZ_ASSERT(oldProto->GetScriptableInfo()->GetScriptableShared() == michael@0: newProto->GetScriptableInfo()->GetScriptableShared(), michael@0: "Changing proto is also changing JSObject Classname or " michael@0: "helper's nsIXPScriptable flags. This is not allowed!"); michael@0: michael@0: wrapper->UpdateScriptableInfo(newProto->GetScriptableInfo()); michael@0: } michael@0: michael@0: // Crash if the wrapper is already in the new scope. michael@0: if (newMap->Find(wrapper->GetIdentityObject())) michael@0: MOZ_CRASH(); michael@0: michael@0: if (!newMap->Add(wrapper)) michael@0: MOZ_CRASH(); michael@0: michael@0: flat = xpc::TransplantObject(cx, flat, newobj); michael@0: if (!flat) michael@0: MOZ_CRASH(); michael@0: michael@0: MOZ_ASSERT(flat); michael@0: wrapper->mFlatJSObject = flat; michael@0: wrapper->mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); michael@0: michael@0: if (cache) { michael@0: bool preserving = cache->PreservingWrapper(); michael@0: cache->SetPreservingWrapper(false); michael@0: cache->SetWrapper(flat); michael@0: cache->SetPreservingWrapper(preserving); michael@0: } michael@0: if (!JS_CopyPropertiesFrom(cx, flat, propertyHolder)) michael@0: MOZ_CRASH(); michael@0: michael@0: // Call the scriptable hook to indicate that we transplanted. michael@0: XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo(); michael@0: if (si->GetFlags().WantPostCreate()) michael@0: (void) si->GetCallback()->PostTransplant(wrapper, cx, flat); michael@0: } michael@0: michael@0: // Now we can just fix up the parent and return the wrapper michael@0: michael@0: if (aNewParent) { michael@0: if (!JS_SetParent(cx, flat, aNewParent)) michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Orphans are sad little things - If only we could treat them better. :-( michael@0: // michael@0: // When a wrapper gets reparented to another scope (for example, when calling michael@0: // adoptNode), it's entirely possible that it previously served as the parent for michael@0: // other wrappers (via PreCreate hooks). When it moves, the old mFlatJSObject is michael@0: // replaced by a cross-compartment wrapper. Its descendants really _should_ move michael@0: // too, but we have no way of locating them short of a compartment-wide sweep michael@0: // (which we believe to be prohibitively expensive). michael@0: // michael@0: // So we just leave them behind. In practice, the only time this turns out to michael@0: // be a problem is during subsequent wrapper reparenting. When this happens, we michael@0: // call into the below fixup code at the last minute and straighten things out michael@0: // before proceeding. michael@0: // michael@0: // See bug 751995 for more information. michael@0: michael@0: static nsresult michael@0: RescueOrphans(HandleObject obj) michael@0: { michael@0: AutoJSContext cx; michael@0: // michael@0: // Even if we're not an orphan at the moment, one of our ancestors might michael@0: // be. If so, we need to recursively rescue up the parent chain. michael@0: // michael@0: michael@0: // First, get the parent object. If we're currently an orphan, the parent michael@0: // object is a cross-compartment wrapper. Follow the parent into its own michael@0: // compartment and fix it up there. We'll fix up |this| afterwards. michael@0: // michael@0: // NB: We pass stopAtOuter=false during the unwrap because Location objects michael@0: // are parented to outer window proxies. michael@0: nsresult rv; michael@0: RootedObject parentObj(cx, js::GetObjectParent(obj)); michael@0: if (!parentObj) michael@0: return NS_OK; // Global object. We're done. michael@0: parentObj = js::UncheckedUnwrap(parentObj, /* stopAtOuter = */ false); michael@0: michael@0: // PreCreate may touch dead compartments. michael@0: js::AutoMaybeTouchDeadZones agc(parentObj); michael@0: michael@0: // Recursively fix up orphans on the parent chain. michael@0: rv = RescueOrphans(parentObj); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Now that we know our parent is in the right place, determine if we've michael@0: // been orphaned. If not, we have nothing to do. michael@0: if (!js::IsCrossCompartmentWrapper(parentObj)) michael@0: return NS_OK; michael@0: michael@0: // We've been orphaned. Find where our parent went, and follow it. michael@0: if (IS_WN_REFLECTOR(obj)) { michael@0: RootedObject realParent(cx, js::UncheckedUnwrap(parentObj)); michael@0: XPCWrappedNative *wn = michael@0: static_cast(js::GetObjectPrivate(obj)); michael@0: return wn->ReparentWrapperIfFound(GetObjectScope(parentObj), michael@0: GetObjectScope(realParent), michael@0: realParent, wn->GetIdentityObject()); michael@0: } michael@0: michael@0: JSAutoCompartment ac(cx, obj); michael@0: return ReparentWrapper(cx, obj); michael@0: } michael@0: michael@0: // Recursively fix up orphans on the parent chain of a wrapper. Note that this michael@0: // can cause a wrapper to move even if it is not an orphan, since its parent michael@0: // might be an orphan and fixing the parent causes this wrapper to become an michael@0: // orphan. michael@0: nsresult michael@0: XPCWrappedNative::RescueOrphans() michael@0: { michael@0: AutoJSContext cx; michael@0: RootedObject flatJSObject(cx, mFlatJSObject); michael@0: return ::RescueOrphans(flatJSObject); michael@0: } michael@0: michael@0: bool michael@0: XPCWrappedNative::ExtendSet(XPCNativeInterface* aInterface) michael@0: { michael@0: AutoJSContext cx; michael@0: michael@0: if (!mSet->HasInterface(aInterface)) { michael@0: AutoMarkingNativeSetPtr newSet(cx); michael@0: newSet = XPCNativeSet::GetNewOrUsed(mSet, aInterface, michael@0: mSet->GetInterfaceCount()); michael@0: if (!newSet) michael@0: return false; michael@0: michael@0: mSet = newSet; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: XPCWrappedNativeTearOff* michael@0: XPCWrappedNative::LocateTearOff(XPCNativeInterface* aInterface) michael@0: { michael@0: for (XPCWrappedNativeTearOffChunk* chunk = &mFirstChunk; michael@0: chunk != nullptr; michael@0: chunk = chunk->mNextChunk) { michael@0: XPCWrappedNativeTearOff* tearOff = chunk->mTearOffs; michael@0: XPCWrappedNativeTearOff* const end = tearOff + michael@0: XPC_WRAPPED_NATIVE_TEAROFFS_PER_CHUNK; michael@0: for (tearOff = chunk->mTearOffs; michael@0: tearOff < end; michael@0: tearOff++) { michael@0: if (tearOff->GetInterface() == aInterface) { michael@0: return tearOff; michael@0: } michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: XPCWrappedNativeTearOff* michael@0: XPCWrappedNative::FindTearOff(XPCNativeInterface* aInterface, michael@0: bool needJSObject /* = false */, michael@0: nsresult* pError /* = nullptr */) michael@0: { michael@0: AutoJSContext cx; michael@0: nsresult rv = NS_OK; michael@0: XPCWrappedNativeTearOff* to; michael@0: XPCWrappedNativeTearOff* firstAvailable = nullptr; michael@0: michael@0: XPCWrappedNativeTearOffChunk* lastChunk; michael@0: XPCWrappedNativeTearOffChunk* chunk; michael@0: for (lastChunk = chunk = &mFirstChunk; michael@0: chunk; michael@0: lastChunk = chunk, chunk = chunk->mNextChunk) { michael@0: to = chunk->mTearOffs; michael@0: XPCWrappedNativeTearOff* const end = chunk->mTearOffs + michael@0: XPC_WRAPPED_NATIVE_TEAROFFS_PER_CHUNK; michael@0: for (to = chunk->mTearOffs; michael@0: to < end; michael@0: to++) { michael@0: if (to->GetInterface() == aInterface) { michael@0: if (needJSObject && !to->GetJSObjectPreserveColor()) { michael@0: AutoMarkingWrappedNativeTearOffPtr tearoff(cx, to); michael@0: bool ok = InitTearOffJSObject(to); michael@0: // During shutdown, we don't sweep tearoffs. So make sure michael@0: // to unmark manually in case the auto-marker marked us. michael@0: // We shouldn't ever be getting here _during_ our michael@0: // Mark/Sweep cycle, so this should be safe. michael@0: to->Unmark(); michael@0: if (!ok) { michael@0: to = nullptr; michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: if (pError) michael@0: *pError = rv; michael@0: return to; michael@0: } michael@0: if (!firstAvailable && to->IsAvailable()) michael@0: firstAvailable = to; michael@0: } michael@0: } michael@0: michael@0: to = firstAvailable; michael@0: michael@0: if (!to) { michael@0: auto newChunk = new XPCWrappedNativeTearOffChunk(); michael@0: lastChunk->mNextChunk = newChunk; michael@0: to = newChunk->mTearOffs; michael@0: } michael@0: michael@0: { michael@0: // Scope keeps |tearoff| from leaking across the rest of the function. michael@0: AutoMarkingWrappedNativeTearOffPtr tearoff(cx, to); michael@0: rv = InitTearOff(to, aInterface, needJSObject); michael@0: // During shutdown, we don't sweep tearoffs. So make sure to unmark michael@0: // manually in case the auto-marker marked us. We shouldn't ever be michael@0: // getting here _during_ our Mark/Sweep cycle, so this should be safe. michael@0: to->Unmark(); michael@0: if (NS_FAILED(rv)) michael@0: to = nullptr; michael@0: } michael@0: michael@0: if (pError) michael@0: *pError = rv; michael@0: return to; michael@0: } michael@0: michael@0: nsresult michael@0: XPCWrappedNative::InitTearOff(XPCWrappedNativeTearOff* aTearOff, michael@0: XPCNativeInterface* aInterface, michael@0: bool needJSObject) michael@0: { michael@0: AutoJSContext cx; michael@0: michael@0: // Determine if the object really does this interface... michael@0: michael@0: const nsIID* iid = aInterface->GetIID(); michael@0: nsISupports* identity = GetIdentityObject(); michael@0: nsISupports* obj; michael@0: michael@0: // If the scriptable helper forbids us from reflecting additional michael@0: // interfaces, then don't even try the QI, just fail. michael@0: if (mScriptableInfo && michael@0: mScriptableInfo->GetFlags().ClassInfoInterfacesOnly() && michael@0: !mSet->HasInterface(aInterface) && michael@0: !mSet->HasInterfaceWithAncestor(aInterface)) { michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: // We are about to call out to other code. michael@0: // So protect our intended tearoff. michael@0: michael@0: aTearOff->SetReserved(); michael@0: michael@0: if (NS_FAILED(identity->QueryInterface(*iid, (void**)&obj)) || !obj) { michael@0: aTearOff->SetInterface(nullptr); michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: // Guard against trying to build a tearoff for a shared nsIClassInfo. michael@0: if (iid->Equals(NS_GET_IID(nsIClassInfo))) { michael@0: nsCOMPtr alternate_identity(do_QueryInterface(obj)); michael@0: if (alternate_identity.get() != identity) { michael@0: NS_RELEASE(obj); michael@0: aTearOff->SetInterface(nullptr); michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: } michael@0: michael@0: // Guard against trying to build a tearoff for an interface that is michael@0: // aggregated and is implemented as a nsIXPConnectWrappedJS using this michael@0: // self-same JSObject. The XBL system does this. If we mutate the set michael@0: // of this wrapper then we will shadow the method that XBL has added to michael@0: // the JSObject that it has inserted in the JS proto chain between our michael@0: // JSObject and our XPCWrappedNativeProto's JSObject. If we let this michael@0: // set mutation happen then the interface's methods will be added to michael@0: // our JSObject, but calls on those methods will get routed up to michael@0: // native code and into the wrappedJS - which will do a method lookup michael@0: // on *our* JSObject and find the same method and make another call michael@0: // into an infinite loop. michael@0: // see: http://bugzilla.mozilla.org/show_bug.cgi?id=96725 michael@0: michael@0: // The code in this block also does a check for the double wrapped michael@0: // nsIPropertyBag case. michael@0: michael@0: nsCOMPtr wrappedJS(do_QueryInterface(obj)); michael@0: if (wrappedJS) { michael@0: RootedObject jso(cx, wrappedJS->GetJSObject()); michael@0: if (jso == mFlatJSObject) { michael@0: // The implementing JSObject is the same as ours! Just say OK michael@0: // without actually extending the set. michael@0: // michael@0: // XXX It is a little cheesy to have FindTearOff return an michael@0: // 'empty' tearoff. But this is the centralized place to do the michael@0: // QI activities on the underlying object. *And* most caller to michael@0: // FindTearOff only look for a non-null result and ignore the michael@0: // actual tearoff returned. The only callers that do use the michael@0: // returned tearoff make sure to check for either a non-null michael@0: // JSObject or a matching Interface before proceeding. michael@0: // I think we can get away with this bit of ugliness. michael@0: michael@0: NS_RELEASE(obj); michael@0: aTearOff->SetInterface(nullptr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Decide whether or not to expose nsIPropertyBag to calling michael@0: // JS code in the double wrapped case. michael@0: // michael@0: // Our rule here is that when JSObjects are double wrapped and michael@0: // exposed to other JSObjects then the nsIPropertyBag interface michael@0: // is only exposed on an 'opt-in' basis; i.e. if the underlying michael@0: // JSObject wants other JSObjects to be able to see this interface michael@0: // then it must implement QueryInterface and not throw an exception michael@0: // when asked for nsIPropertyBag. It need not actually *implement* michael@0: // nsIPropertyBag - xpconnect will do that work. michael@0: michael@0: if (iid->Equals(NS_GET_IID(nsIPropertyBag)) && jso) { michael@0: nsRefPtr clasp = nsXPCWrappedJSClass::GetNewOrUsed(cx, *iid); michael@0: if (clasp) { michael@0: RootedObject answer(cx, clasp->CallQueryInterfaceOnJSObject(cx, jso, *iid)); michael@0: michael@0: if (!answer) { michael@0: NS_RELEASE(obj); michael@0: aTearOff->SetInterface(nullptr); michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIXPCSecurityManager* sm = nsXPConnect::XPConnect()->GetDefaultSecurityManager(); michael@0: if (sm && NS_FAILED(sm-> michael@0: CanCreateWrapper(cx, *iid, identity, michael@0: GetClassInfo()))) { michael@0: // the security manager vetoed. It should have set an exception. michael@0: NS_RELEASE(obj); michael@0: aTearOff->SetInterface(nullptr); michael@0: return NS_ERROR_XPC_SECURITY_MANAGER_VETO; michael@0: } michael@0: michael@0: // If this is not already in our set we need to extend our set. michael@0: // Note: we do not cache the result of the previous call to HasInterface() michael@0: // because we unlocked and called out in the interim and the result of the michael@0: // previous call might not be correct anymore. michael@0: michael@0: if (!mSet->HasInterface(aInterface) && !ExtendSet(aInterface)) { michael@0: NS_RELEASE(obj); michael@0: aTearOff->SetInterface(nullptr); michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: aTearOff->SetInterface(aInterface); michael@0: aTearOff->SetNative(obj); michael@0: if (needJSObject && !InitTearOffJSObject(aTearOff)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: XPCWrappedNative::InitTearOffJSObject(XPCWrappedNativeTearOff* to) michael@0: { michael@0: AutoJSContext cx; michael@0: michael@0: RootedObject parent(cx, mFlatJSObject); michael@0: RootedObject proto(cx, JS_GetObjectPrototype(cx, parent)); michael@0: JSObject* obj = JS_NewObject(cx, Jsvalify(&XPC_WN_Tearoff_JSClass), michael@0: proto, parent); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JS_SetPrivate(obj, to); michael@0: to->SetJSObject(obj); michael@0: return true; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: static bool Throw(nsresult errNum, XPCCallContext& ccx) michael@0: { michael@0: XPCThrower::Throw(errNum, ccx); michael@0: return false; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: class MOZ_STACK_CLASS CallMethodHelper michael@0: { michael@0: XPCCallContext& mCallContext; michael@0: // We wait to call SetLastResult(mInvokeResult) until ~CallMethodHelper(), michael@0: // so that XPCWN-implemented functions like XPCComponents::GetLastResult() michael@0: // can still access the previous result. michael@0: nsresult mInvokeResult; michael@0: nsIInterfaceInfo* const mIFaceInfo; michael@0: const nsXPTMethodInfo* mMethodInfo; michael@0: nsISupports* const mCallee; michael@0: const uint16_t mVTableIndex; michael@0: HandleId mIdxValueId; michael@0: michael@0: nsAutoTArray mDispatchParams; michael@0: uint8_t mJSContextIndex; // TODO make const michael@0: uint8_t mOptArgcIndex; // TODO make const michael@0: michael@0: jsval* const mArgv; michael@0: const uint32_t mArgc; michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: GetArraySizeFromParam(uint8_t paramIndex, uint32_t* result) const; michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: GetInterfaceTypeFromParam(uint8_t paramIndex, michael@0: const nsXPTType& datum_type, michael@0: nsID* result) const; michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: GetOutParamSource(uint8_t paramIndex, MutableHandleValue srcp) const; michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: GatherAndConvertResults(); michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: QueryInterfaceFastPath(); michael@0: michael@0: nsXPTCVariant* michael@0: GetDispatchParam(uint8_t paramIndex) michael@0: { michael@0: if (paramIndex >= mJSContextIndex) michael@0: paramIndex += 1; michael@0: if (paramIndex >= mOptArgcIndex) michael@0: paramIndex += 1; michael@0: return &mDispatchParams[paramIndex]; michael@0: } michael@0: const nsXPTCVariant* michael@0: GetDispatchParam(uint8_t paramIndex) const michael@0: { michael@0: return const_cast(this)->GetDispatchParam(paramIndex); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool InitializeDispatchParams(); michael@0: michael@0: MOZ_ALWAYS_INLINE bool ConvertIndependentParams(bool* foundDependentParam); michael@0: MOZ_ALWAYS_INLINE bool ConvertIndependentParam(uint8_t i); michael@0: MOZ_ALWAYS_INLINE bool ConvertDependentParams(); michael@0: MOZ_ALWAYS_INLINE bool ConvertDependentParam(uint8_t i); michael@0: michael@0: MOZ_ALWAYS_INLINE void CleanupParam(nsXPTCMiniVariant& param, nsXPTType& type); michael@0: michael@0: MOZ_ALWAYS_INLINE bool HandleDipperParam(nsXPTCVariant* dp, michael@0: const nsXPTParamInfo& paramInfo); michael@0: michael@0: MOZ_ALWAYS_INLINE nsresult Invoke(); michael@0: michael@0: public: michael@0: michael@0: CallMethodHelper(XPCCallContext& ccx) michael@0: : mCallContext(ccx) michael@0: , mInvokeResult(NS_ERROR_UNEXPECTED) michael@0: , mIFaceInfo(ccx.GetInterface()->GetInterfaceInfo()) michael@0: , mMethodInfo(nullptr) michael@0: , mCallee(ccx.GetTearOff()->GetNative()) michael@0: , mVTableIndex(ccx.GetMethodIndex()) michael@0: , mIdxValueId(ccx.GetRuntime()->GetStringID(XPCJSRuntime::IDX_VALUE)) michael@0: , mJSContextIndex(UINT8_MAX) michael@0: , mOptArgcIndex(UINT8_MAX) michael@0: , mArgv(ccx.GetArgv()) michael@0: , mArgc(ccx.GetArgc()) michael@0: michael@0: { michael@0: // Success checked later. michael@0: mIFaceInfo->GetMethodInfo(mVTableIndex, &mMethodInfo); michael@0: } michael@0: michael@0: ~CallMethodHelper(); michael@0: michael@0: MOZ_ALWAYS_INLINE bool Call(); michael@0: michael@0: }; michael@0: michael@0: // static michael@0: bool michael@0: XPCWrappedNative::CallMethod(XPCCallContext& ccx, michael@0: CallMode mode /*= CALL_METHOD */) michael@0: { michael@0: MOZ_ASSERT(ccx.GetXPCContext()->CallerTypeIsJavaScript(), michael@0: "Native caller for XPCWrappedNative::CallMethod?"); michael@0: michael@0: nsresult rv = ccx.CanCallNow(); michael@0: if (NS_FAILED(rv)) { michael@0: return Throw(rv, ccx); michael@0: } michael@0: michael@0: return CallMethodHelper(ccx).Call(); michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::Call() michael@0: { michael@0: mCallContext.SetRetVal(JSVAL_VOID); michael@0: michael@0: XPCJSRuntime::Get()->SetPendingException(nullptr); michael@0: michael@0: if (mVTableIndex == 0) { michael@0: return QueryInterfaceFastPath(); michael@0: } michael@0: michael@0: if (!mMethodInfo) { michael@0: Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, mCallContext); michael@0: return false; michael@0: } michael@0: michael@0: if (!InitializeDispatchParams()) michael@0: return false; michael@0: michael@0: // Iterate through the params doing conversions of independent params only. michael@0: // When we later convert the dependent params (if any) we will know that michael@0: // the params upon which they depend will have already been converted - michael@0: // regardless of ordering. michael@0: bool foundDependentParam = false; michael@0: if (!ConvertIndependentParams(&foundDependentParam)) michael@0: return false; michael@0: michael@0: if (foundDependentParam && !ConvertDependentParams()) michael@0: return false; michael@0: michael@0: mInvokeResult = Invoke(); michael@0: michael@0: if (JS_IsExceptionPending(mCallContext)) { michael@0: return false; michael@0: } michael@0: michael@0: if (NS_FAILED(mInvokeResult)) { michael@0: ThrowBadResult(mInvokeResult, mCallContext); michael@0: return false; michael@0: } michael@0: michael@0: return GatherAndConvertResults(); michael@0: } michael@0: michael@0: CallMethodHelper::~CallMethodHelper() michael@0: { michael@0: uint8_t paramCount = mMethodInfo->GetParamCount(); michael@0: if (mDispatchParams.Length()) { michael@0: for (uint8_t i = 0; i < paramCount; i++) { michael@0: nsXPTCVariant* dp = GetDispatchParam(i); michael@0: const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); michael@0: michael@0: if (paramInfo.GetType().IsArray()) { michael@0: void* p = dp->val.p; michael@0: if (!p) michael@0: continue; michael@0: michael@0: // Clean up the array contents if necessary. michael@0: if (dp->DoesValNeedCleanup()) { michael@0: // We need some basic information to properly destroy the array. michael@0: uint32_t array_count = 0; michael@0: nsXPTType datum_type; michael@0: if (!GetArraySizeFromParam(i, &array_count) || michael@0: !NS_SUCCEEDED(mIFaceInfo->GetTypeForParam(mVTableIndex, michael@0: ¶mInfo, michael@0: 1, &datum_type))) { michael@0: // XXXbholley - I'm not convinced that the above calls will michael@0: // ever fail. michael@0: NS_ERROR("failed to get array information, we'll leak here"); michael@0: continue; michael@0: } michael@0: michael@0: // Loop over the array contents. For each one, we create a michael@0: // dummy 'val' and pass it to the cleanup helper. michael@0: for (uint32_t k = 0; k < array_count; k++) { michael@0: nsXPTCMiniVariant v; michael@0: v.val.p = static_cast(p)[k]; michael@0: CleanupParam(v, datum_type); michael@0: } michael@0: } michael@0: michael@0: // always free the array itself michael@0: nsMemory::Free(p); michael@0: } else { michael@0: // Clean up single parameters (if requested). michael@0: if (dp->DoesValNeedCleanup()) michael@0: CleanupParam(*dp, dp->type); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mCallContext.GetXPCContext()->SetLastResult(mInvokeResult); michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::GetArraySizeFromParam(uint8_t paramIndex, michael@0: uint32_t* result) const michael@0: { michael@0: nsresult rv; michael@0: const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(paramIndex); michael@0: michael@0: // TODO fixup the various exceptions that are thrown michael@0: michael@0: rv = mIFaceInfo->GetSizeIsArgNumberForParam(mVTableIndex, ¶mInfo, 0, ¶mIndex); michael@0: if (NS_FAILED(rv)) michael@0: return Throw(NS_ERROR_XPC_CANT_GET_ARRAY_INFO, mCallContext); michael@0: michael@0: *result = GetDispatchParam(paramIndex)->val.u32; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::GetInterfaceTypeFromParam(uint8_t paramIndex, michael@0: const nsXPTType& datum_type, michael@0: nsID* result) const michael@0: { michael@0: nsresult rv; michael@0: const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(paramIndex); michael@0: uint8_t tag = datum_type.TagPart(); michael@0: michael@0: // TODO fixup the various exceptions that are thrown michael@0: michael@0: if (tag == nsXPTType::T_INTERFACE) { michael@0: rv = mIFaceInfo->GetIIDForParamNoAlloc(mVTableIndex, ¶mInfo, result); michael@0: if (NS_FAILED(rv)) michael@0: return ThrowBadParam(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, michael@0: paramIndex, mCallContext); michael@0: } else if (tag == nsXPTType::T_INTERFACE_IS) { michael@0: rv = mIFaceInfo->GetInterfaceIsArgNumberForParam(mVTableIndex, ¶mInfo, michael@0: ¶mIndex); michael@0: if (NS_FAILED(rv)) michael@0: return Throw(NS_ERROR_XPC_CANT_GET_ARRAY_INFO, mCallContext); michael@0: michael@0: nsID* p = (nsID*) GetDispatchParam(paramIndex)->val.p; michael@0: if (!p) michael@0: return ThrowBadParam(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, michael@0: paramIndex, mCallContext); michael@0: *result = *p; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::GetOutParamSource(uint8_t paramIndex, MutableHandleValue srcp) const michael@0: { michael@0: const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(paramIndex); michael@0: michael@0: if ((paramInfo.IsOut() || paramInfo.IsDipper()) && michael@0: !paramInfo.IsRetval()) { michael@0: MOZ_ASSERT(paramIndex < mArgc || paramInfo.IsOptional(), michael@0: "Expected either enough arguments or an optional argument"); michael@0: jsval arg = paramIndex < mArgc ? mArgv[paramIndex] : JSVAL_NULL; michael@0: if (paramIndex < mArgc) { michael@0: RootedObject obj(mCallContext); michael@0: if (!arg.isPrimitive()) michael@0: obj = &arg.toObject(); michael@0: if (!obj || !JS_GetPropertyById(mCallContext, obj, mIdxValueId, srcp)) { michael@0: // Explicitly passed in unusable value for out param. Note michael@0: // that if i >= mArgc we already know that |arg| is JSVAL_NULL, michael@0: // and that's ok. michael@0: ThrowBadParam(NS_ERROR_XPC_NEED_OUT_OBJECT, paramIndex, michael@0: mCallContext); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::GatherAndConvertResults() michael@0: { michael@0: // now we iterate through the native params to gather and convert results michael@0: uint8_t paramCount = mMethodInfo->GetParamCount(); michael@0: for (uint8_t i = 0; i < paramCount; i++) { michael@0: const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); michael@0: if (!paramInfo.IsOut() && !paramInfo.IsDipper()) michael@0: continue; michael@0: michael@0: const nsXPTType& type = paramInfo.GetType(); michael@0: nsXPTCVariant* dp = GetDispatchParam(i); michael@0: RootedValue v(mCallContext, NullValue()); michael@0: uint32_t array_count = 0; michael@0: nsXPTType datum_type; michael@0: bool isArray = type.IsArray(); michael@0: bool isSizedString = isArray ? michael@0: false : michael@0: type.TagPart() == nsXPTType::T_PSTRING_SIZE_IS || michael@0: type.TagPart() == nsXPTType::T_PWSTRING_SIZE_IS; michael@0: michael@0: if (isArray) { michael@0: if (NS_FAILED(mIFaceInfo->GetTypeForParam(mVTableIndex, ¶mInfo, 1, michael@0: &datum_type))) { michael@0: Throw(NS_ERROR_XPC_CANT_GET_ARRAY_INFO, mCallContext); michael@0: return false; michael@0: } michael@0: } else michael@0: datum_type = type; michael@0: michael@0: if (isArray || isSizedString) { michael@0: if (!GetArraySizeFromParam(i, &array_count)) michael@0: return false; michael@0: } michael@0: michael@0: nsID param_iid; michael@0: if (datum_type.IsInterfacePointer() && michael@0: !GetInterfaceTypeFromParam(i, datum_type, ¶m_iid)) michael@0: return false; michael@0: michael@0: nsresult err; michael@0: if (isArray) { michael@0: if (!XPCConvert::NativeArray2JS(&v, (const void**)&dp->val, michael@0: datum_type, ¶m_iid, michael@0: array_count, &err)) { michael@0: // XXX need exception scheme for arrays to indicate bad element michael@0: ThrowBadParam(err, i, mCallContext); michael@0: return false; michael@0: } michael@0: } else if (isSizedString) { michael@0: if (!XPCConvert::NativeStringWithSize2JS(&v, michael@0: (const void*)&dp->val, michael@0: datum_type, michael@0: array_count, &err)) { michael@0: ThrowBadParam(err, i, mCallContext); michael@0: return false; michael@0: } michael@0: } else { michael@0: if (!XPCConvert::NativeData2JS(&v, &dp->val, datum_type, michael@0: ¶m_iid, &err)) { michael@0: ThrowBadParam(err, i, mCallContext); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (paramInfo.IsRetval()) { michael@0: mCallContext.SetRetVal(v); michael@0: } else if (i < mArgc) { michael@0: // we actually assured this before doing the invoke michael@0: MOZ_ASSERT(mArgv[i].isObject(), "out var is not object"); michael@0: RootedObject obj(mCallContext, &mArgv[i].toObject()); michael@0: if (!JS_SetPropertyById(mCallContext, obj, mIdxValueId, v)) { michael@0: ThrowBadParam(NS_ERROR_XPC_CANT_SET_OUT_VAL, i, mCallContext); michael@0: return false; michael@0: } michael@0: } else { michael@0: MOZ_ASSERT(paramInfo.IsOptional(), michael@0: "Expected either enough arguments or an optional argument"); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::QueryInterfaceFastPath() michael@0: { michael@0: MOZ_ASSERT(mVTableIndex == 0, michael@0: "Using the QI fast-path for a method other than QueryInterface"); michael@0: michael@0: if (mArgc < 1) { michael@0: Throw(NS_ERROR_XPC_NOT_ENOUGH_ARGS, mCallContext); michael@0: return false; michael@0: } michael@0: michael@0: if (!mArgv[0].isObject()) { michael@0: ThrowBadParam(NS_ERROR_XPC_BAD_CONVERT_JS, 0, mCallContext); michael@0: return false; michael@0: } michael@0: michael@0: const nsID* iid = xpc_JSObjectToID(mCallContext, &mArgv[0].toObject()); michael@0: if (!iid) { michael@0: ThrowBadParam(NS_ERROR_XPC_BAD_CONVERT_JS, 0, mCallContext); michael@0: return false; michael@0: } michael@0: michael@0: nsISupports* qiresult = nullptr; michael@0: mInvokeResult = mCallee->QueryInterface(*iid, (void**) &qiresult); michael@0: michael@0: if (NS_FAILED(mInvokeResult)) { michael@0: ThrowBadResult(mInvokeResult, mCallContext); michael@0: return false; michael@0: } michael@0: michael@0: RootedValue v(mCallContext, NullValue()); michael@0: nsresult err; michael@0: bool success = michael@0: XPCConvert::NativeData2JS(&v, &qiresult, michael@0: nsXPTType::T_INTERFACE_IS, michael@0: iid, &err); michael@0: NS_IF_RELEASE(qiresult); michael@0: michael@0: if (!success) { michael@0: ThrowBadParam(err, 0, mCallContext); michael@0: return false; michael@0: } michael@0: michael@0: mCallContext.SetRetVal(v); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::InitializeDispatchParams() michael@0: { michael@0: const uint8_t wantsOptArgc = mMethodInfo->WantsOptArgc() ? 1 : 0; michael@0: const uint8_t wantsJSContext = mMethodInfo->WantsContext() ? 1 : 0; michael@0: const uint8_t paramCount = mMethodInfo->GetParamCount(); michael@0: uint8_t requiredArgs = paramCount; michael@0: uint8_t hasRetval = 0; michael@0: michael@0: // XXX ASSUMES that retval is last arg. The xpidl compiler ensures this. michael@0: if (paramCount && mMethodInfo->GetParam(paramCount-1).IsRetval()) { michael@0: hasRetval = 1; michael@0: requiredArgs--; michael@0: } michael@0: michael@0: if (mArgc < requiredArgs || wantsOptArgc) { michael@0: if (wantsOptArgc) michael@0: mOptArgcIndex = requiredArgs; michael@0: michael@0: // skip over any optional arguments michael@0: while (requiredArgs && mMethodInfo->GetParam(requiredArgs-1).IsOptional()) michael@0: requiredArgs--; michael@0: michael@0: if (mArgc < requiredArgs) { michael@0: Throw(NS_ERROR_XPC_NOT_ENOUGH_ARGS, mCallContext); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (wantsJSContext) { michael@0: if (wantsOptArgc) michael@0: // Need to bump mOptArgcIndex up one here. michael@0: mJSContextIndex = mOptArgcIndex++; michael@0: else if (mMethodInfo->IsSetter() || mMethodInfo->IsGetter()) michael@0: // For attributes, we always put the JSContext* first. michael@0: mJSContextIndex = 0; michael@0: else michael@0: mJSContextIndex = paramCount - hasRetval; michael@0: } michael@0: michael@0: // iterate through the params to clear flags (for safe cleanup later) michael@0: for (uint8_t i = 0; i < paramCount + wantsJSContext + wantsOptArgc; i++) { michael@0: nsXPTCVariant* dp = mDispatchParams.AppendElement(); michael@0: dp->ClearFlags(); michael@0: dp->val.p = nullptr; michael@0: } michael@0: michael@0: // Fill in the JSContext argument michael@0: if (wantsJSContext) { michael@0: nsXPTCVariant* dp = &mDispatchParams[mJSContextIndex]; michael@0: dp->type = nsXPTType::T_VOID; michael@0: dp->val.p = mCallContext; michael@0: } michael@0: michael@0: // Fill in the optional_argc argument michael@0: if (wantsOptArgc) { michael@0: nsXPTCVariant* dp = &mDispatchParams[mOptArgcIndex]; michael@0: dp->type = nsXPTType::T_U8; michael@0: dp->val.u8 = std::min(mArgc, paramCount) - requiredArgs; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::ConvertIndependentParams(bool* foundDependentParam) michael@0: { michael@0: const uint8_t paramCount = mMethodInfo->GetParamCount(); michael@0: for (uint8_t i = 0; i < paramCount; i++) { michael@0: const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); michael@0: michael@0: if (paramInfo.GetType().IsDependent()) michael@0: *foundDependentParam = true; michael@0: else if (!ConvertIndependentParam(i)) michael@0: return false; michael@0: michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::ConvertIndependentParam(uint8_t i) michael@0: { michael@0: const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); michael@0: const nsXPTType& type = paramInfo.GetType(); michael@0: uint8_t type_tag = type.TagPart(); michael@0: nsXPTCVariant* dp = GetDispatchParam(i); michael@0: dp->type = type; michael@0: MOZ_ASSERT(!paramInfo.IsShared(), "[shared] implies [noscript]!"); michael@0: michael@0: // Handle dipper types separately. michael@0: if (paramInfo.IsDipper()) michael@0: return HandleDipperParam(dp, paramInfo); michael@0: michael@0: // Specify the correct storage/calling semantics. michael@0: if (paramInfo.IsIndirect()) michael@0: dp->SetIndirect(); michael@0: michael@0: // The JSVal proper is always stored within the 'val' union and passed michael@0: // indirectly, regardless of in/out-ness. michael@0: if (type_tag == nsXPTType::T_JSVAL) { michael@0: // Root the value. michael@0: dp->val.j = JSVAL_VOID; michael@0: if (!js::AddRawValueRoot(mCallContext, &dp->val.j, "XPCWrappedNative::CallMethod param")) michael@0: return false; michael@0: } michael@0: michael@0: // Flag cleanup for anything that isn't self-contained. michael@0: if (!type.IsArithmetic()) michael@0: dp->SetValNeedsCleanup(); michael@0: michael@0: // Even if there's nothing to convert, we still need to examine the michael@0: // JSObject container for out-params. If it's null or otherwise invalid, michael@0: // we want to know before the call, rather than after. michael@0: // michael@0: // This is a no-op for 'in' params. michael@0: RootedValue src(mCallContext); michael@0: if (!GetOutParamSource(i, &src)) michael@0: return false; michael@0: michael@0: // All that's left to do is value conversion. Bail early if we don't need michael@0: // to do that. michael@0: if (!paramInfo.IsIn()) michael@0: return true; michael@0: michael@0: // We're definitely some variety of 'in' now, so there's something to michael@0: // convert. The source value for conversion depends on whether we're michael@0: // dealing with an 'in' or an 'inout' parameter. 'inout' was handled above, michael@0: // so all that's left is 'in'. michael@0: if (!paramInfo.IsOut()) { michael@0: // Handle the 'in' case. michael@0: MOZ_ASSERT(i < mArgc || paramInfo.IsOptional(), michael@0: "Expected either enough arguments or an optional argument"); michael@0: if (i < mArgc) michael@0: src = mArgv[i]; michael@0: else if (type_tag == nsXPTType::T_JSVAL) michael@0: src = JSVAL_VOID; michael@0: else michael@0: src = JSVAL_NULL; michael@0: } michael@0: michael@0: nsID param_iid; michael@0: if (type_tag == nsXPTType::T_INTERFACE && michael@0: NS_FAILED(mIFaceInfo->GetIIDForParamNoAlloc(mVTableIndex, ¶mInfo, michael@0: ¶m_iid))) { michael@0: ThrowBadParam(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, i, mCallContext); michael@0: return false; michael@0: } michael@0: michael@0: nsresult err; michael@0: if (!XPCConvert::JSData2Native(&dp->val, src, type, true, ¶m_iid, &err)) { michael@0: ThrowBadParam(err, i, mCallContext); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::ConvertDependentParams() michael@0: { michael@0: const uint8_t paramCount = mMethodInfo->GetParamCount(); michael@0: for (uint8_t i = 0; i < paramCount; i++) { michael@0: const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); michael@0: michael@0: if (!paramInfo.GetType().IsDependent()) michael@0: continue; michael@0: if (!ConvertDependentParam(i)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CallMethodHelper::ConvertDependentParam(uint8_t i) michael@0: { michael@0: const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); michael@0: const nsXPTType& type = paramInfo.GetType(); michael@0: nsXPTType datum_type; michael@0: uint32_t array_count = 0; michael@0: bool isArray = type.IsArray(); michael@0: michael@0: bool isSizedString = isArray ? michael@0: false : michael@0: type.TagPart() == nsXPTType::T_PSTRING_SIZE_IS || michael@0: type.TagPart() == nsXPTType::T_PWSTRING_SIZE_IS; michael@0: michael@0: nsXPTCVariant* dp = GetDispatchParam(i); michael@0: dp->type = type; michael@0: michael@0: if (isArray) { michael@0: if (NS_FAILED(mIFaceInfo->GetTypeForParam(mVTableIndex, ¶mInfo, 1, michael@0: &datum_type))) { michael@0: Throw(NS_ERROR_XPC_CANT_GET_ARRAY_INFO, mCallContext); michael@0: return false; michael@0: } michael@0: MOZ_ASSERT(datum_type.TagPart() != nsXPTType::T_JSVAL, michael@0: "Arrays of JSVals not currently supported - see bug 693337."); michael@0: } else { michael@0: datum_type = type; michael@0: } michael@0: michael@0: // Specify the correct storage/calling semantics. michael@0: if (paramInfo.IsIndirect()) michael@0: dp->SetIndirect(); michael@0: michael@0: // We have 3 possible type of dependent parameters: Arrays, Sized Strings, michael@0: // and iid_is Interface pointers. The latter two always need cleanup, and michael@0: // arrays need cleanup for all non-arithmetic types. Since the latter two michael@0: // cases also happen to be non-arithmetic, we can just inspect datum_type michael@0: // here. michael@0: if (!datum_type.IsArithmetic()) michael@0: dp->SetValNeedsCleanup(); michael@0: michael@0: // Even if there's nothing to convert, we still need to examine the michael@0: // JSObject container for out-params. If it's null or otherwise invalid, michael@0: // we want to know before the call, rather than after. michael@0: // michael@0: // This is a no-op for 'in' params. michael@0: RootedValue src(mCallContext); michael@0: if (!GetOutParamSource(i, &src)) michael@0: return false; michael@0: michael@0: // All that's left to do is value conversion. Bail early if we don't need michael@0: // to do that. michael@0: if (!paramInfo.IsIn()) michael@0: return true; michael@0: michael@0: // We're definitely some variety of 'in' now, so there's something to michael@0: // convert. The source value for conversion depends on whether we're michael@0: // dealing with an 'in' or an 'inout' parameter. 'inout' was handled above, michael@0: // so all that's left is 'in'. michael@0: if (!paramInfo.IsOut()) { michael@0: // Handle the 'in' case. michael@0: MOZ_ASSERT(i < mArgc || paramInfo.IsOptional(), michael@0: "Expected either enough arguments or an optional argument"); michael@0: src = i < mArgc ? mArgv[i] : JSVAL_NULL; michael@0: } michael@0: michael@0: nsID param_iid; michael@0: if (datum_type.IsInterfacePointer() && michael@0: !GetInterfaceTypeFromParam(i, datum_type, ¶m_iid)) michael@0: return false; michael@0: michael@0: nsresult err; michael@0: michael@0: if (isArray || isSizedString) { michael@0: if (!GetArraySizeFromParam(i, &array_count)) michael@0: return false; michael@0: michael@0: if (isArray) { michael@0: if (array_count && michael@0: !XPCConvert::JSArray2Native((void**)&dp->val, src, michael@0: array_count, datum_type, ¶m_iid, michael@0: &err)) { michael@0: // XXX need exception scheme for arrays to indicate bad element michael@0: ThrowBadParam(err, i, mCallContext); michael@0: return false; michael@0: } michael@0: } else // if (isSizedString) michael@0: { michael@0: if (!XPCConvert::JSStringWithSize2Native((void*)&dp->val, michael@0: src, array_count, michael@0: datum_type, &err)) { michael@0: ThrowBadParam(err, i, mCallContext); michael@0: return false; michael@0: } michael@0: } michael@0: } else { michael@0: if (!XPCConvert::JSData2Native(&dp->val, src, type, true, michael@0: ¶m_iid, &err)) { michael@0: ThrowBadParam(err, i, mCallContext); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Performs all necessary teardown on a parameter after method invocation. michael@0: // michael@0: // This method should only be called if the value in question was flagged michael@0: // for cleanup (ie, if dp->DoesValNeedCleanup()). michael@0: void michael@0: CallMethodHelper::CleanupParam(nsXPTCMiniVariant& param, nsXPTType& type) michael@0: { michael@0: // We handle array elements, but not the arrays themselves. michael@0: MOZ_ASSERT(type.TagPart() != nsXPTType::T_ARRAY, "Can't handle arrays."); michael@0: michael@0: // Pointers may sometimes be null even if cleanup was requested. Combine michael@0: // the null checking for all the different types into one check here. michael@0: if (type.TagPart() != nsXPTType::T_JSVAL && param.val.p == nullptr) michael@0: return; michael@0: michael@0: switch (type.TagPart()) { michael@0: case nsXPTType::T_JSVAL: michael@0: js::RemoveRawValueRoot(mCallContext, (jsval*)¶m.val); michael@0: break; michael@0: case nsXPTType::T_INTERFACE: michael@0: case nsXPTType::T_INTERFACE_IS: michael@0: ((nsISupports*)param.val.p)->Release(); michael@0: break; michael@0: case nsXPTType::T_ASTRING: michael@0: case nsXPTType::T_DOMSTRING: michael@0: nsXPConnect::GetRuntimeInstance()->DeleteShortLivedString((nsString*)param.val.p); michael@0: break; michael@0: case nsXPTType::T_UTF8STRING: michael@0: case nsXPTType::T_CSTRING: michael@0: { michael@0: nsCString* rs = (nsCString*)param.val.p; michael@0: if (rs != &EmptyCString() && rs != &NullCString()) michael@0: delete rs; michael@0: } michael@0: break; michael@0: default: michael@0: MOZ_ASSERT(!type.IsArithmetic(), "Cleanup requested on unexpected type."); michael@0: nsMemory::Free(param.val.p); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Handle parameters with dipper types. michael@0: // michael@0: // Dipper types are one of the more inscrutable aspects of xpidl. In a michael@0: // nutshell, dippers are empty container objects, created and passed by michael@0: // the caller, and filled by the callee. The callee receives a michael@0: // fully-formed object, and thus does not have to construct anything. But michael@0: // the object is functionally empty, and the callee is responsible for michael@0: // putting something useful inside of it. michael@0: // michael@0: // XPIDL decides which types to make dippers. The list of these types michael@0: // is given in the isDipperType() function in typelib.py, and is currently michael@0: // limited to 4 string types. michael@0: // michael@0: // When a dipper type is declared as an 'out' parameter, xpidl internally michael@0: // converts it to an 'in', and sets the XPT_PD_DIPPER flag on it. For this michael@0: // reason, dipper types are sometimes referred to as 'out parameters michael@0: // masquerading as in'. The burden of maintaining this illusion falls mostly michael@0: // on XPConnect - we create the empty containers, and harvest the results michael@0: // after the call. michael@0: // michael@0: // This method creates these empty containers. michael@0: bool michael@0: CallMethodHelper::HandleDipperParam(nsXPTCVariant* dp, michael@0: const nsXPTParamInfo& paramInfo) michael@0: { michael@0: // Get something we can make comparisons with. michael@0: uint8_t type_tag = paramInfo.GetType().TagPart(); michael@0: michael@0: // Dippers always have the 'in' and 'dipper' flags set. Never 'out'. michael@0: MOZ_ASSERT(!paramInfo.IsOut(), "Dipper has unexpected flags."); michael@0: michael@0: // xpidl.h specifies that dipper types will be used in exactly four michael@0: // cases, all strings. Verify that here. michael@0: MOZ_ASSERT(type_tag == nsXPTType::T_ASTRING || michael@0: type_tag == nsXPTType::T_DOMSTRING || michael@0: type_tag == nsXPTType::T_UTF8STRING || michael@0: type_tag == nsXPTType::T_CSTRING, michael@0: "Unexpected dipper type!"); michael@0: michael@0: // ASTRING and DOMSTRING are very similar, and both use nsString. michael@0: // UTF8_STRING and CSTRING are also quite similar, and both use nsCString. michael@0: if (type_tag == nsXPTType::T_ASTRING || type_tag == nsXPTType::T_DOMSTRING) michael@0: dp->val.p = nsXPConnect::GetRuntimeInstance()->NewShortLivedString(); michael@0: else michael@0: dp->val.p = new nsCString(); michael@0: michael@0: // Check for OOM, in either case. michael@0: if (!dp->val.p) { michael@0: JS_ReportOutOfMemory(mCallContext); michael@0: return false; michael@0: } michael@0: michael@0: // We allocated, so we need to deallocate after the method call completes. michael@0: dp->SetValNeedsCleanup(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: CallMethodHelper::Invoke() michael@0: { michael@0: uint32_t argc = mDispatchParams.Length(); michael@0: nsXPTCVariant* argv = mDispatchParams.Elements(); michael@0: michael@0: return NS_InvokeByIndex(mCallee, mVTableIndex, argc, argv); michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: // interface methods michael@0: michael@0: /* JSObjectPtr GetJSObject(); */ michael@0: JSObject* michael@0: XPCWrappedNative::GetJSObject() michael@0: { michael@0: return GetFlatJSObject(); michael@0: } michael@0: michael@0: /* readonly attribute nsISupports Native; */ michael@0: NS_IMETHODIMP XPCWrappedNative::GetNative(nsISupports * *aNative) michael@0: { michael@0: // No need to QI here, we already have the correct nsISupports michael@0: // vtable. michael@0: nsCOMPtr rval = mIdentity; michael@0: rval.forget(aNative); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* reaonly attribute JSObjectPtr JSObjectPrototype; */ michael@0: NS_IMETHODIMP XPCWrappedNative::GetJSObjectPrototype(JSObject * *aJSObjectPrototype) michael@0: { michael@0: *aJSObjectPrototype = HasProto() ? michael@0: GetProto()->GetJSProtoObject() : GetFlatJSObject(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIPrincipal* michael@0: XPCWrappedNative::GetObjectPrincipal() const michael@0: { michael@0: nsIPrincipal* principal = GetScope()->GetPrincipal(); michael@0: #ifdef DEBUG michael@0: // Because of inner window reuse, we can have objects with one principal michael@0: // living in a scope with a different (but same-origin) principal. So michael@0: // just check same-origin here. michael@0: nsCOMPtr objPrin(do_QueryInterface(mIdentity)); michael@0: if (objPrin) { michael@0: bool equal; michael@0: if (!principal) michael@0: equal = !objPrin->GetPrincipal(); michael@0: else michael@0: principal->Equals(objPrin->GetPrincipal(), &equal); michael@0: MOZ_ASSERT(equal, "Principal mismatch. Expect bad things to happen"); michael@0: } michael@0: #endif michael@0: return principal; michael@0: } michael@0: michael@0: /* XPCNativeInterface FindInterfaceWithMember (in JSHandleId name); */ michael@0: NS_IMETHODIMP XPCWrappedNative::FindInterfaceWithMember(HandleId name, michael@0: nsIInterfaceInfo * *_retval) michael@0: { michael@0: XPCNativeInterface* iface; michael@0: XPCNativeMember* member; michael@0: michael@0: if (GetSet()->FindMember(name, &member, &iface) && iface) { michael@0: nsCOMPtr temp = iface->GetInterfaceInfo(); michael@0: temp.forget(_retval); michael@0: } else michael@0: *_retval = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* XPCNativeInterface FindInterfaceWithName (in JSHandleId name); */ michael@0: NS_IMETHODIMP XPCWrappedNative::FindInterfaceWithName(HandleId name, michael@0: nsIInterfaceInfo * *_retval) michael@0: { michael@0: XPCNativeInterface* iface = GetSet()->FindNamedInterface(name); michael@0: if (iface) { michael@0: nsCOMPtr temp = iface->GetInterfaceInfo(); michael@0: temp.forget(_retval); michael@0: } else michael@0: *_retval = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* [notxpcom] bool HasNativeMember (in JSHandleId name); */ michael@0: NS_IMETHODIMP_(bool) michael@0: XPCWrappedNative::HasNativeMember(HandleId name) michael@0: { michael@0: XPCNativeMember *member = nullptr; michael@0: uint16_t ignored; michael@0: return GetSet()->FindMember(name, &member, &ignored) && !!member; michael@0: } michael@0: michael@0: /* void finishInitForWrappedGlobal (); */ michael@0: NS_IMETHODIMP XPCWrappedNative::FinishInitForWrappedGlobal() michael@0: { michael@0: // We can only be called under certain conditions. michael@0: MOZ_ASSERT(mScriptableInfo); michael@0: MOZ_ASSERT(mScriptableInfo->GetFlags().IsGlobalObject()); michael@0: MOZ_ASSERT(HasProto()); michael@0: michael@0: // Call PostCreateProrotype. michael@0: bool success = GetProto()->CallPostCreatePrototype(); michael@0: if (!success) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void debugDump (in short depth); */ michael@0: NS_IMETHODIMP XPCWrappedNative::DebugDump(int16_t depth) michael@0: { michael@0: #ifdef DEBUG michael@0: depth-- ; michael@0: XPC_LOG_ALWAYS(("XPCWrappedNative @ %x with mRefCnt = %d", this, mRefCnt.get())); michael@0: XPC_LOG_INDENT(); michael@0: michael@0: if (HasProto()) { michael@0: XPCWrappedNativeProto* proto = GetProto(); michael@0: if (depth && proto) michael@0: proto->DebugDump(depth); michael@0: else michael@0: XPC_LOG_ALWAYS(("mMaybeProto @ %x", proto)); michael@0: } else michael@0: XPC_LOG_ALWAYS(("Scope @ %x", GetScope())); michael@0: michael@0: if (depth && mSet) michael@0: mSet->DebugDump(depth); michael@0: else michael@0: XPC_LOG_ALWAYS(("mSet @ %x", mSet)); michael@0: michael@0: XPC_LOG_ALWAYS(("mFlatJSObject of %x", mFlatJSObject.getPtr())); michael@0: XPC_LOG_ALWAYS(("mIdentity of %x", mIdentity)); michael@0: XPC_LOG_ALWAYS(("mScriptableInfo @ %x", mScriptableInfo)); michael@0: michael@0: if (depth && mScriptableInfo) { michael@0: XPC_LOG_INDENT(); michael@0: XPC_LOG_ALWAYS(("mScriptable @ %x", mScriptableInfo->GetCallback())); michael@0: XPC_LOG_ALWAYS(("mFlags of %x", (uint32_t)mScriptableInfo->GetFlags())); michael@0: XPC_LOG_ALWAYS(("mJSClass @ %x", mScriptableInfo->GetJSClass())); michael@0: XPC_LOG_OUTDENT(); michael@0: } michael@0: XPC_LOG_OUTDENT(); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: char* michael@0: XPCWrappedNative::ToString(XPCWrappedNativeTearOff* to /* = nullptr */ ) const michael@0: { michael@0: #ifdef DEBUG michael@0: # define FMT_ADDR " @ 0x%p" michael@0: # define FMT_STR(str) str michael@0: # define PARAM_ADDR(w) , w michael@0: #else michael@0: # define FMT_ADDR "" michael@0: # define FMT_STR(str) michael@0: # define PARAM_ADDR(w) michael@0: #endif michael@0: michael@0: char* sz = nullptr; michael@0: char* name = nullptr; michael@0: michael@0: XPCNativeScriptableInfo* si = GetScriptableInfo(); michael@0: if (si) michael@0: name = JS_smprintf("%s", si->GetJSClass()->name); michael@0: if (to) { michael@0: const char* fmt = name ? " (%s)" : "%s"; michael@0: name = JS_sprintf_append(name, fmt, michael@0: to->GetInterface()->GetNameString()); michael@0: } else if (!name) { michael@0: XPCNativeSet* set = GetSet(); michael@0: XPCNativeInterface** array = set->GetInterfaceArray(); michael@0: uint16_t count = set->GetInterfaceCount(); michael@0: michael@0: if (count == 1) michael@0: name = JS_sprintf_append(name, "%s", array[0]->GetNameString()); michael@0: else if (count == 2 && michael@0: array[0] == XPCNativeInterface::GetISupports()) { michael@0: name = JS_sprintf_append(name, "%s", array[1]->GetNameString()); michael@0: } else { michael@0: for (uint16_t i = 0; i < count; i++) { michael@0: const char* fmt = (i == 0) ? michael@0: "(%s" : (i == count-1) ? michael@0: ", %s)" : ", %s"; michael@0: name = JS_sprintf_append(name, fmt, michael@0: array[i]->GetNameString()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!name) { michael@0: return nullptr; michael@0: } michael@0: const char* fmt = "[xpconnect wrapped %s" FMT_ADDR FMT_STR(" (native") michael@0: FMT_ADDR FMT_STR(")") "]"; michael@0: if (si) { michael@0: fmt = "[object %s" FMT_ADDR FMT_STR(" (native") FMT_ADDR FMT_STR(")") "]"; michael@0: } michael@0: sz = JS_smprintf(fmt, name PARAM_ADDR(this) PARAM_ADDR(mIdentity)); michael@0: michael@0: JS_smprintf_free(name); michael@0: michael@0: michael@0: return sz; michael@0: michael@0: #undef FMT_ADDR michael@0: #undef PARAM_ADDR michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: #ifdef XPC_CHECK_CLASSINFO_CLAIMS michael@0: static void DEBUG_CheckClassInfoClaims(XPCWrappedNative* wrapper) michael@0: { michael@0: if (!wrapper || !wrapper->GetClassInfo()) michael@0: return; michael@0: michael@0: nsISupports* obj = wrapper->GetIdentityObject(); michael@0: XPCNativeSet* set = wrapper->GetSet(); michael@0: uint16_t count = set->GetInterfaceCount(); michael@0: for (uint16_t i = 0; i < count; i++) { michael@0: nsIClassInfo* clsInfo = wrapper->GetClassInfo(); michael@0: XPCNativeInterface* iface = set->GetInterfaceAt(i); michael@0: nsIInterfaceInfo* info = iface->GetInterfaceInfo(); michael@0: const nsIID* iid; michael@0: nsISupports* ptr; michael@0: michael@0: info->GetIIDShared(&iid); michael@0: nsresult rv = obj->QueryInterface(*iid, (void**)&ptr); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: NS_RELEASE(ptr); michael@0: continue; michael@0: } michael@0: if (rv == NS_ERROR_OUT_OF_MEMORY) michael@0: continue; michael@0: michael@0: // Houston, We have a problem... michael@0: michael@0: char* className = nullptr; michael@0: char* contractID = nullptr; michael@0: const char* interfaceName; michael@0: michael@0: info->GetNameShared(&interfaceName); michael@0: clsInfo->GetContractID(&contractID); michael@0: if (wrapper->GetScriptableInfo()) { michael@0: wrapper->GetScriptableInfo()->GetCallback()-> michael@0: GetClassName(&className); michael@0: } michael@0: michael@0: michael@0: printf("\n!!! Object's nsIClassInfo lies about its interfaces!!!\n" michael@0: " classname: %s \n" michael@0: " contractid: %s \n" michael@0: " unimplemented interface name: %s\n\n", michael@0: className ? className : "", michael@0: contractID ? contractID : "", michael@0: interfaceName); michael@0: michael@0: if (className) michael@0: nsMemory::Free(className); michael@0: if (contractID) michael@0: nsMemory::Free(contractID); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: NS_IMPL_ISUPPORTS(XPCJSObjectHolder, nsIXPConnectJSObjectHolder) michael@0: michael@0: JSObject* michael@0: XPCJSObjectHolder::GetJSObject() michael@0: { michael@0: NS_PRECONDITION(mJSObj, "bad object state"); michael@0: return mJSObj; michael@0: } michael@0: michael@0: XPCJSObjectHolder::XPCJSObjectHolder(JSObject* obj) michael@0: : mJSObj(obj) michael@0: { michael@0: XPCJSRuntime::Get()->AddObjectHolderRoot(this); michael@0: } michael@0: michael@0: XPCJSObjectHolder::~XPCJSObjectHolder() michael@0: { michael@0: RemoveFromRootSet(); michael@0: } michael@0: michael@0: void michael@0: XPCJSObjectHolder::TraceJS(JSTracer *trc) michael@0: { michael@0: trc->setTracingDetails(GetTraceName, this, 0); michael@0: JS_CallHeapObjectTracer(trc, &mJSObj, "XPCJSObjectHolder::mJSObj"); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: XPCJSObjectHolder::GetTraceName(JSTracer* trc, char *buf, size_t bufsize) michael@0: { michael@0: JS_snprintf(buf, bufsize, "XPCJSObjectHolder[0x%p].mJSObj", michael@0: trc->debugPrintArg()); michael@0: } michael@0: michael@0: // static michael@0: XPCJSObjectHolder* michael@0: XPCJSObjectHolder::newHolder(JSObject* obj) michael@0: { michael@0: if (!obj) { michael@0: NS_ERROR("bad param"); michael@0: return nullptr; michael@0: } michael@0: return new XPCJSObjectHolder(obj); michael@0: }