summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src/XPCWrappedJS.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /js/xpconnect/src/XPCWrappedJS.cpp
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/xpconnect/src/XPCWrappedJS.cpp')
-rw-r--r--js/xpconnect/src/XPCWrappedJS.cpp686
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;
+}