diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/xpconnect/src/XPCWrappedJS.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | js/xpconnect/src/XPCWrappedJS.cpp | 686 |
1 files changed, 686 insertions, 0 deletions
diff --git a/js/xpconnect/src/XPCWrappedJS.cpp b/js/xpconnect/src/XPCWrappedJS.cpp new file mode 100644 index 0000000000..d01c801600 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedJS.cpp @@ -0,0 +1,686 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Class that wraps JS objects to appear as XPCOM objects. */ + +#include "xpcprivate.h" +#include "XPCMaps.h" +#include "mozilla/DeferredFinalize.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/Sprintf.h" +#include "js/Object.h" // JS::GetCompartment +#include "js/RealmIterators.h" +#include "nsCCUncollectableMarker.h" +#include "nsContentUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +// NOTE: much of the fancy footwork is done in xpcstubs.cpp + +// nsXPCWrappedJS lifetime. +// +// An nsXPCWrappedJS is either rooting its JS object or is subject to +// finalization. The subject-to-finalization state lets wrappers support +// nsSupportsWeakReference in the case where the underlying JS object +// is strongly owned, but the wrapper itself is only weakly owned. +// +// A wrapper is rooting its JS object whenever its refcount is greater than 1. +// In this state, root wrappers are always added to the cycle collector graph. +// The wrapper keeps around an extra refcount, added in the constructor, to +// support the possibility of an eventual transition to the +// subject-to-finalization state. This extra refcount is ignored by the cycle +// collector, which traverses the "self" edge for this refcount. +// +// When the refcount of a rooting wrapper drops to 1, if there is no weak +// reference to the wrapper (which can only happen for the root wrapper), it is +// immediately Destroy()'d. Otherwise, it becomes subject to finalization. +// +// When a wrapper is subject to finalization, the wrapper has a refcount of 1. +// It is now owned exclusively by its JS object. Either a weak reference will be +// turned into a strong ref which will bring its refcount up to 2 and change the +// wrapper back to the rooting state, or it will stay alive until the JS object +// dies. If the JS object dies, then when +// JSObject2WrappedJSMap::UpdateWeakPointersAfterGC is called (via the JS +// engine's weak pointer zone or compartment callbacks) it will find the wrapper +// and call Release() on it, destroying the wrapper. Otherwise, the wrapper will +// stay alive, even if it no longer has a weak reference to it. +// +// When the wrapper is subject to finalization, it is kept alive by an implicit +// reference from the JS object which is invisible to the cycle collector, so +// the cycle collector does not traverse any children of wrappers that are +// subject to finalization. This will result in a leak if a wrapper in the +// non-rooting state has an aggregated native that keeps alive the wrapper's JS +// object. See bug 947049. + +// If traversing wrappedJS wouldn't release it, nor cause any other objects to +// be added to the graph, there is no need to add it to the graph at all. +bool nsXPCWrappedJS::CanSkip() { + if (!nsCCUncollectableMarker::sGeneration) { + return false; + } + + // If this wrapper holds a gray object, need to trace it. + // We can't skip it even if it is subject to finalization, because we want to + // be able to collect it if the JS object is gray. + JSObject* obj = GetJSObjectPreserveColor(); + if (obj && JS::ObjectIsMarkedGray(obj)) { + return false; + } + + // For non-root wrappers, check if the root wrapper will be + // added to the CC graph. + if (!IsRootWrapper()) { + // mRoot points to null after unlinking. + NS_ENSURE_TRUE(mRoot, false); + return mRoot->CanSkip(); + } + + // At this point, the WJS must be a root wrapper with a black JS object, so + // if it is subject to finalization, the JS object will be holding it alive + // so it will be okay to skip it. + + // For the root wrapper, check if there is an aggregated + // native object that will be added to the CC graph. + if (!IsAggregatedToNative()) { + return true; + } + + nsISupports* agg = GetAggregatedNativeObject(); + nsXPCOMCycleCollectionParticipant* cp = nullptr; + CallQueryInterface(agg, &cp); + nsISupports* canonical = nullptr; + agg->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast<void**>(&canonical)); + return cp && canonical && cp->CanSkipThis(canonical); +} + +NS_IMETHODIMP +NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::TraverseNative( + void* p, nsCycleCollectionTraversalCallback& cb) { + nsISupports* s = static_cast<nsISupports*>(p); + MOZ_ASSERT(CheckForRightISupports(s), + "not the nsISupports pointer we expect"); + nsXPCWrappedJS* tmp = Downcast(s); + + nsrefcnt refcnt = tmp->mRefCnt.get(); + if (cb.WantDebugInfo()) { + char name[72]; + SprintfLiteral(name, "nsXPCWrappedJS (%s)", tmp->mInfo->Name()); + cb.DescribeRefCountedNode(refcnt, name); + } else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsXPCWrappedJS, refcnt) + } + + if (tmp->IsSubjectToFinalization()) { + // If the WJS is subject to finalization, then it can be held alive by its + // JS object. We represent this edge by using NoteWeakMapping. The linked + // list of subject-to-finalization WJS acts like a known-black weak map. + cb.NoteWeakMapping(tmp->GetJSObjectPreserveColor(), s, + NS_CYCLE_COLLECTION_PARTICIPANT(nsXPCWrappedJS)); + } + + // Don't let the extra reference for nsSupportsWeakReference keep a WJS alive. + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "self"); + cb.NoteXPCOMChild(s); + + if (tmp->IsRootWrapper()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "aggregated native"); + cb.NoteXPCOMChild(tmp->GetAggregatedNativeObject()); + } else { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "root"); + cb.NoteXPCOMChild(ToSupports(tmp->GetRootWrapper())); + } + + return NS_OK; +} + +NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS(nsXPCWrappedJS) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXPCWrappedJS) + tmp->Unlink(); + // Note: Unlink already calls ClearWeakReferences, so no need for + // NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE here. +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXPCWrappedJS) + // See the comment at the top of this file for the explanation of + // the weird tracing condition. + if (!tmp->IsSubjectToFinalization()) { + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJSObj) + } +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +// WJS are JS holders, so we'll always add them as roots in CCs and we can +// remove them from the purple buffer in between CCs. +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXPCWrappedJS) + return true; +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXPCWrappedJS) + return tmp->CanSkip(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXPCWrappedJS) + return tmp->CanSkip(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +nsXPCWrappedJS* nsIXPConnectWrappedJS::AsXPCWrappedJS() { + return static_cast<nsXPCWrappedJS*>(this); +} + +nsresult nsIXPConnectWrappedJS::AggregatedQueryInterface(REFNSIID aIID, + void** aInstancePtr) { + MOZ_ASSERT(AsXPCWrappedJS()->IsAggregatedToNative(), + "bad AggregatedQueryInterface call"); + *aInstancePtr = nullptr; + + if (!AsXPCWrappedJS()->IsValid()) { + return NS_ERROR_UNEXPECTED; + } + + // Put this here rather that in DelegatedQueryInterface because it needs + // to be in QueryInterface before the possible delegation to 'outer', but + // we don't want to do this check twice in one call in the normal case: + // once in QueryInterface and once in DelegatedQueryInterface. + if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJS))) { + NS_ADDREF(this); + *aInstancePtr = (void*)this; + return NS_OK; + } + + return AsXPCWrappedJS()->DelegatedQueryInterface(aIID, aInstancePtr); +} + +NS_IMETHODIMP +nsXPCWrappedJS::QueryInterface(REFNSIID aIID, void** aInstancePtr) { + if (nullptr == aInstancePtr) { + MOZ_ASSERT(false, "null pointer"); + return NS_ERROR_NULL_POINTER; + } + + *aInstancePtr = nullptr; + + if (aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant))) { + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(nsXPCWrappedJS); + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsCycleCollectionISupports))) { + *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this); + return NS_OK; + } + + if (!IsValid()) { + return NS_ERROR_UNEXPECTED; + } + + if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJSUnmarkGray))) { + *aInstancePtr = nullptr; + + mJSObj.exposeToActiveJS(); + + // Just return some error value since one isn't supposed to use + // nsIXPConnectWrappedJSUnmarkGray objects for anything. + return NS_ERROR_FAILURE; + } + + // Always check for this first so that our 'outer' can get this interface + // from us without recurring into a call to the outer's QI! + if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJS))) { + NS_ADDREF(this); + *aInstancePtr = (void*)static_cast<nsIXPConnectWrappedJS*>(this); + return NS_OK; + } + + nsISupports* outer = GetAggregatedNativeObject(); + if (outer) { + return outer->QueryInterface(aIID, aInstancePtr); + } + + // else... + + return DelegatedQueryInterface(aIID, aInstancePtr); +} + +// For a description of nsXPCWrappedJS lifetime and reference counting, see +// the comment at the top of this file. + +MozExternalRefCountType nsXPCWrappedJS::AddRef(void) { + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::AddRef called off main thread"); + + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + nsISupports* base = + NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this); + nsrefcnt cnt = mRefCnt.incr(base); + NS_LOG_ADDREF(this, cnt, "nsXPCWrappedJS", sizeof(*this)); + + if (2 == cnt && IsValid()) { + GetJSObject(); // Unmark gray JSObject. + + // This WJS is no longer subject to finalization. + if (isInList()) { + remove(); + } + } + + return cnt; +} + +MozExternalRefCountType nsXPCWrappedJS::Release(void) { + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::Release called off main thread"); + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + NS_ASSERT_OWNINGTHREAD(nsXPCWrappedJS); + + bool shouldDelete = false; + nsISupports* base = + NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this); + nsrefcnt cnt = mRefCnt.decr(base, &shouldDelete); + NS_LOG_RELEASE(this, cnt, "nsXPCWrappedJS"); + + if (0 == cnt) { + if (MOZ_UNLIKELY(shouldDelete)) { + mRefCnt.stabilizeForDeletion(); + DeleteCycleCollectable(); + } else { + mRefCnt.incr(base); + Destroy(); + mRefCnt.decr(base); + } + } else if (1 == cnt) { + // If we are not a root wrapper being used from a weak reference, + // then the extra ref is not needed and we can let ourselves be + // deleted. + if (!HasWeakReferences()) { + return Release(); + } + + if (IsValid()) { + XPCJSRuntime::Get()->AddSubjectToFinalizationWJS(this); + } + + MOZ_ASSERT(IsRootWrapper(), + "Only root wrappers should have weak references"); + } + return cnt; +} + +NS_IMETHODIMP_(void) +nsXPCWrappedJS::DeleteCycleCollectable(void) { delete this; } + +NS_IMETHODIMP +nsXPCWrappedJS::GetWeakReference(nsIWeakReference** aInstancePtr) { + if (!IsRootWrapper()) { + return mRoot->GetWeakReference(aInstancePtr); + } + + return nsSupportsWeakReference::GetWeakReference(aInstancePtr); +} + +JSObject* nsXPCWrappedJS::GetJSObject() { return mJSObj; } + +JSObject* nsIXPConnectWrappedJS::GetJSObjectGlobal() { + JSObject* obj = AsXPCWrappedJS()->mJSObj; + if (js::IsCrossCompartmentWrapper(obj)) { + JS::Compartment* comp = JS::GetCompartment(obj); + return js::GetFirstGlobalInCompartment(comp); + } + return JS::GetNonCCWObjectGlobal(obj); +} + +// static +nsresult nsXPCWrappedJS::GetNewOrUsed(JSContext* cx, JS::HandleObject jsObj, + REFNSIID aIID, + nsXPCWrappedJS** wrapperResult) { + // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread. + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::GetNewOrUsed called off main thread"); + + MOZ_RELEASE_ASSERT(js::GetContextCompartment(cx) == + JS::GetCompartment(jsObj)); + + const nsXPTInterfaceInfo* info = GetInterfaceInfo(aIID); + if (!info) { + return NS_ERROR_FAILURE; + } + + JS::RootedObject rootJSObj(cx, GetRootJSObject(cx, jsObj)); + if (!rootJSObj) { + return NS_ERROR_FAILURE; + } + + xpc::CompartmentPrivate* rootComp = xpc::CompartmentPrivate::Get(rootJSObj); + MOZ_ASSERT(rootComp); + + // Find any existing wrapper. + RefPtr<nsXPCWrappedJS> root = rootComp->GetWrappedJSMap()->Find(rootJSObj); + MOZ_ASSERT_IF(root, !nsXPConnect::GetRuntimeInstance() + ->GetMultiCompartmentWrappedJSMap() + ->Find(rootJSObj)); + if (!root) { + root = nsXPConnect::GetRuntimeInstance() + ->GetMultiCompartmentWrappedJSMap() + ->Find(rootJSObj); + } + + nsresult rv = NS_ERROR_FAILURE; + if (root) { + RefPtr<nsXPCWrappedJS> wrapper = root->FindOrFindInherited(aIID); + if (wrapper) { + wrapper.forget(wrapperResult); + return NS_OK; + } + } else if (rootJSObj != jsObj) { + // Make a new root wrapper, because there is no existing + // root wrapper, and the wrapper we are trying to make isn't + // a root. + const nsXPTInterfaceInfo* rootInfo = + GetInterfaceInfo(NS_GET_IID(nsISupports)); + if (!rootInfo) { + return NS_ERROR_FAILURE; + } + + root = new nsXPCWrappedJS(cx, rootJSObj, rootInfo, nullptr, &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + RefPtr<nsXPCWrappedJS> wrapper = + new nsXPCWrappedJS(cx, jsObj, info, root, &rv); + if (NS_FAILED(rv)) { + return rv; + } + wrapper.forget(wrapperResult); + return NS_OK; +} + +nsXPCWrappedJS::nsXPCWrappedJS(JSContext* cx, JSObject* aJSObj, + const nsXPTInterfaceInfo* aInfo, + nsXPCWrappedJS* root, nsresult* rv) + : mJSObj(aJSObj), mInfo(aInfo), mRoot(root ? root : this), mNext(nullptr) { + *rv = InitStub(mInfo->IID()); + // Continue even in the failure case, so that our refcounting/Destroy + // behavior works correctly. + + // There is an extra AddRef to support weak references to wrappers + // that are subject to finalization. See the top of the file for more + // details. + NS_ADDREF_THIS(); + + if (IsRootWrapper()) { + MOZ_ASSERT(!IsMultiCompartment(), "mNext is always nullptr here"); + if (!xpc::CompartmentPrivate::Get(mJSObj)->GetWrappedJSMap()->Add(cx, + this)) { + *rv = NS_ERROR_OUT_OF_MEMORY; + } + } else { + NS_ADDREF(mRoot); + mNext = mRoot->mNext; + mRoot->mNext = this; + + // We always start wrappers in the per-compartment table. If adding + // this wrapper to the chain causes it to cross compartments, we need + // to migrate the chain to the global table on the XPCJSContext. + if (mRoot->IsMultiCompartment()) { + xpc::CompartmentPrivate::Get(mRoot->mJSObj) + ->GetWrappedJSMap() + ->Remove(mRoot); + auto destMap = + nsXPConnect::GetRuntimeInstance()->GetMultiCompartmentWrappedJSMap(); + if (!destMap->Add(cx, mRoot)) { + *rv = NS_ERROR_OUT_OF_MEMORY; + } + } + } + + mozilla::HoldJSObjects(this); +} + +nsXPCWrappedJS::~nsXPCWrappedJS() { Destroy(); } + +void XPCJSRuntime::RemoveWrappedJS(nsXPCWrappedJS* wrapper) { + AssertInvalidWrappedJSNotInTable(wrapper); + if (!wrapper->IsValid()) { + return; + } + + // It is possible for the same JS XPCOM implementation object to be wrapped + // with a different interface in multiple JS::Compartments. In this case, the + // wrapper chain will contain references to multiple compartments. While we + // always store single-compartment chains in the per-compartment wrapped-js + // table, chains in the multi-compartment wrapped-js table may contain + // single-compartment chains, if they have ever contained a wrapper in a + // different compartment. Since removal requires a lookup anyway, we just do + // the remove on both tables unconditionally. + MOZ_ASSERT_IF( + wrapper->IsMultiCompartment(), + !xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor()) + ->GetWrappedJSMap() + ->HasWrapper(wrapper)); + GetMultiCompartmentWrappedJSMap()->Remove(wrapper); + xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor()) + ->GetWrappedJSMap() + ->Remove(wrapper); +} + +#ifdef DEBUG +static JS::CompartmentIterResult NotHasWrapperAssertionCallback( + JSContext* cx, void* data, JS::Compartment* comp) { + auto wrapper = static_cast<nsXPCWrappedJS*>(data); + auto xpcComp = xpc::CompartmentPrivate::Get(comp); + MOZ_ASSERT_IF(xpcComp, !xpcComp->GetWrappedJSMap()->HasWrapper(wrapper)); + return JS::CompartmentIterResult::KeepGoing; +} +#endif + +void XPCJSRuntime::AssertInvalidWrappedJSNotInTable( + nsXPCWrappedJS* wrapper) const { +#ifdef DEBUG + if (!wrapper->IsValid()) { + MOZ_ASSERT(!GetMultiCompartmentWrappedJSMap()->HasWrapper(wrapper)); + if (!mGCIsRunning) { + JSContext* cx = XPCJSContext::Get()->Context(); + JS_IterateCompartments(cx, wrapper, NotHasWrapperAssertionCallback); + } + } +#endif +} + +void nsXPCWrappedJS::Destroy() { + MOZ_ASSERT(1 == int32_t(mRefCnt), "should be stabilized for deletion"); + + if (IsRootWrapper()) { + nsXPConnect::GetRuntimeInstance()->RemoveWrappedJS(this); + } + Unlink(); +} + +void nsXPCWrappedJS::Unlink() { + nsXPConnect::GetRuntimeInstance()->AssertInvalidWrappedJSNotInTable(this); + + if (IsValid()) { + XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance(); + if (rt) { + if (IsRootWrapper()) { + rt->RemoveWrappedJS(this); + } + } + + mJSObj = nullptr; + } + + if (IsRootWrapper()) { + if (isInList()) { + remove(); + } + ClearWeakReferences(); + } else if (mRoot) { + // unlink this wrapper + nsXPCWrappedJS* cur = mRoot; + while (1) { + if (cur->mNext == this) { + cur->mNext = mNext; + break; + } + cur = cur->mNext; + MOZ_ASSERT(cur, "failed to find wrapper in its own chain"); + } + + // Note: unlinking this wrapper may have changed us from a multi- + // compartment wrapper chain to a single-compartment wrapper chain. We + // leave the wrapper in the multi-compartment table as it is likely to + // need to be multi-compartment again in the future and, moreover, we + // cannot get a JSContext here. + + // let the root go + NS_RELEASE(mRoot); + } + + if (mOuter) { + XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance(); + if (rt->GCIsRunning()) { + DeferredFinalize(mOuter.forget().take()); + } else { + mOuter = nullptr; + } + } + + mozilla::DropJSObjects(this); +} + +bool nsXPCWrappedJS::IsMultiCompartment() const { + MOZ_ASSERT(IsRootWrapper()); + JS::Compartment* compartment = Compartment(); + nsXPCWrappedJS* next = mNext; + while (next) { + if (next->Compartment() != compartment) { + return true; + } + next = next->mNext; + } + return false; +} + +nsXPCWrappedJS* nsXPCWrappedJS::Find(REFNSIID aIID) { + if (aIID.Equals(NS_GET_IID(nsISupports))) { + return mRoot; + } + + for (nsXPCWrappedJS* cur = mRoot; cur; cur = cur->mNext) { + if (aIID.Equals(cur->GetIID())) { + return cur; + } + } + + return nullptr; +} + +// check if asking for an interface that some wrapper in the chain inherits from +nsXPCWrappedJS* nsXPCWrappedJS::FindInherited(REFNSIID aIID) { + MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupports)), "bad call sequence"); + + for (nsXPCWrappedJS* cur = mRoot; cur; cur = cur->mNext) { + if (cur->mInfo->HasAncestor(aIID)) { + return cur; + } + } + + return nullptr; +} + +nsresult nsIXPConnectWrappedJS::GetInterfaceIID(nsIID** iid) { + MOZ_ASSERT(iid, "bad param"); + + *iid = AsXPCWrappedJS()->GetIID().Clone(); + return NS_OK; +} + +void nsXPCWrappedJS::SystemIsBeingShutDown() { + // XXX It turns out that it is better to leak here then to do any Releases + // and have them propagate into all sorts of mischief as the system is being + // shutdown. This was learned the hard way :( + + // mJSObj == nullptr is used to indicate that the wrapper is no longer valid + // and that calls should fail without trying to use any of the + // xpconnect mechanisms. 'IsValid' is implemented by checking this pointer. + + // Clear the contents of the pointer using unsafeGet() to avoid + // triggering post barriers in shutdown, as this will access the chunk + // containing mJSObj, which may have been freed at this point. This is safe + // if we are not currently running an incremental GC. + MOZ_ASSERT(!JS::IsIncrementalGCInProgress(xpc_GetSafeJSContext())); + *mJSObj.unsafeGet() = nullptr; + if (isInList()) { + remove(); + } + + // Notify other wrappers in the chain. + if (mNext) { + mNext->SystemIsBeingShutDown(); + } +} + +size_t nsXPCWrappedJS::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + // mJSObject is a JS pointer, so don't measure the object. mInfo is + // not dynamically allocated. mRoot is not measured because it is + // either |this| or we have already measured it. mOuter is rare and + // probably not uniquely owned by this. + size_t n = mallocSizeOf(this); + n += nsAutoXPTCStub::SizeOfExcludingThis(mallocSizeOf); + + // Wrappers form a linked list via the mNext field, so include them all + // in the measurement. Only root wrappers are stored in the map, so + // everything will be measured exactly once. + if (mNext) { + n += mNext->SizeOfIncludingThis(mallocSizeOf); + } + + return n; +} + +/***************************************************************************/ + +nsresult nsIXPConnectWrappedJS::DebugDump(int16_t depth) { + return AsXPCWrappedJS()->DebugDump(depth); +} + +nsresult nsXPCWrappedJS::DebugDump(int16_t depth) { +#ifdef DEBUG + XPC_LOG_ALWAYS( + ("nsXPCWrappedJS @ %p with mRefCnt = %" PRIuPTR, this, mRefCnt.get())); + XPC_LOG_INDENT(); + + XPC_LOG_ALWAYS(("%s wrapper around JSObject @ %p", + IsRootWrapper() ? "ROOT" : "non-root", mJSObj.get())); + const char* name = mInfo->Name(); + XPC_LOG_ALWAYS(("interface name is %s", name)); + auto iid = mInfo->IID().ToString(); + XPC_LOG_ALWAYS(("IID number is %s", iid.get())); + XPC_LOG_ALWAYS(("nsXPTInterfaceInfo @ %p", mInfo)); + + if (!IsRootWrapper()) { + XPC_LOG_OUTDENT(); + } + if (mNext) { + if (IsRootWrapper()) { + XPC_LOG_ALWAYS(("Additional wrappers for this object...")); + XPC_LOG_INDENT(); + } + mNext->DebugDump(depth); + if (IsRootWrapper()) { + XPC_LOG_OUTDENT(); + } + } + if (IsRootWrapper()) { + XPC_LOG_OUTDENT(); + } +#endif + return NS_OK; +} |