/* 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 (true) {
      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.unbarrieredSet(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;
}
