/* -*- 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/. */

#ifndef mozilla_dom_BindingUtils_h__
#define mozilla_dom_BindingUtils_h__

#include <type_traits>

#include "jsfriendapi.h"
#include "js/CharacterEncoding.h"
#include "js/Conversions.h"
#include "js/experimental/JitInfo.h"  // JSJitGetterOp, JSJitInfo
#include "js/friend/WindowProxy.h"  // js::IsWindow, js::IsWindowProxy, js::ToWindowProxyIfWindow
#include "js/MemoryFunctions.h"
#include "js/Object.h"  // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, JS::SetReservedSlot
#include "js/RealmOptions.h"
#include "js/String.h"  // JS::GetLatin1LinearStringChars, JS::GetTwoByteLinearStringChars, JS::GetLinearStringLength, JS::LinearStringHasLatin1Chars, JS::StringHasLatin1Chars
#include "js/Wrapper.h"
#include "js/Zone.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Array.h"
#include "mozilla/Assertions.h"
#include "mozilla/DeferredFinalize.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/BindingCallContext.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/DOMJSClass.h"
#include "mozilla/dom/DOMJSProxyHandler.h"
#include "mozilla/dom/JSSlots.h"
#include "mozilla/dom/NonRefcountedDOMObject.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/PrototypeList.h"
#include "mozilla/dom/RemoteObjectProxy.h"
#include "mozilla/SegmentedVector.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Likely.h"
#include "mozilla/MemoryReporting.h"
#include "nsIGlobalObject.h"
#include "nsJSUtils.h"
#include "nsISupportsImpl.h"
#include "xpcObjectHelper.h"
#include "xpcpublic.h"
#include "nsIVariant.h"
#include "mozilla/dom/FakeString.h"

#include "nsWrapperCacheInlines.h"

class nsGlobalWindowInner;
class nsGlobalWindowOuter;
class nsIInterfaceRequestor;

namespace mozilla {

enum UseCounter : int16_t;
enum class UseCounterWorker : int16_t;

namespace dom {
class CustomElementReactionsStack;
class Document;
class EventTarget;
class MessageManagerGlobal;
class ObservableArrayProxyHandler;
class DedicatedWorkerGlobalScope;
template <typename KeyType, typename ValueType>
class Record;
class WindowProxyHolder;

enum class DeprecatedOperations : uint16_t;

nsresult UnwrapArgImpl(JSContext* cx, JS::Handle<JSObject*> src,
                       const nsIID& iid, void** ppArg);

/** Convert a jsval to an XPCOM pointer. Caller must not assume that src will
    keep the XPCOM pointer rooted. */
template <class Interface>
inline nsresult UnwrapArg(JSContext* cx, JS::Handle<JSObject*> src,
                          Interface** ppArg) {
  return UnwrapArgImpl(cx, src, NS_GET_TEMPLATE_IID(Interface),
                       reinterpret_cast<void**>(ppArg));
}

nsresult UnwrapWindowProxyArg(JSContext* cx, JS::Handle<JSObject*> src,
                              WindowProxyHolder& ppArg);

// Returns true if the JSClass is used for DOM objects.
inline bool IsDOMClass(const JSClass* clasp) {
  return clasp->flags & JSCLASS_IS_DOMJSCLASS;
}

// Return true if the JSClass is used for non-proxy DOM objects.
inline bool IsNonProxyDOMClass(const JSClass* clasp) {
  return IsDOMClass(clasp) && clasp->isNativeObject();
}

// Returns true if the JSClass is used for DOM interface and interface
// prototype objects.
inline bool IsDOMIfaceAndProtoClass(const JSClass* clasp) {
  return clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS;
}

static_assert(DOM_OBJECT_SLOT == 0,
              "DOM_OBJECT_SLOT doesn't match the proxy private slot.  "
              "Expect bad things");
template <class T>
inline T* UnwrapDOMObject(JSObject* obj) {
  MOZ_ASSERT(IsDOMClass(JS::GetClass(obj)),
             "Don't pass non-DOM objects to this function");

  JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
  return static_cast<T*>(val.toPrivate());
}

template <class T>
inline T* UnwrapPossiblyNotInitializedDOMObject(JSObject* obj) {
  // This is used by the OjectMoved JSClass hook which can be called before
  // JS_NewObject has returned and so before we have a chance to set
  // DOM_OBJECT_SLOT to anything useful.

  MOZ_ASSERT(IsDOMClass(JS::GetClass(obj)),
             "Don't pass non-DOM objects to this function");

  JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
  if (val.isUndefined()) {
    return nullptr;
  }
  return static_cast<T*>(val.toPrivate());
}

inline const DOMJSClass* GetDOMClass(const JSClass* clasp) {
  return IsDOMClass(clasp) ? DOMJSClass::FromJSClass(clasp) : nullptr;
}

inline const DOMJSClass* GetDOMClass(JSObject* obj) {
  return GetDOMClass(JS::GetClass(obj));
}

inline nsISupports* UnwrapDOMObjectToISupports(JSObject* aObject) {
  const DOMJSClass* clasp = GetDOMClass(aObject);
  if (!clasp || !clasp->mDOMObjectIsISupports) {
    return nullptr;
  }

  return UnwrapPossiblyNotInitializedDOMObject<nsISupports>(aObject);
}

inline bool IsDOMObject(JSObject* obj) { return IsDOMClass(JS::GetClass(obj)); }

// There are two valid ways to use UNWRAP_OBJECT: Either obj needs to
// be a MutableHandle<JSObject*>, or value needs to be a strong-reference
// smart pointer type (OwningNonNull or RefPtr or nsCOMPtr), in which case obj
// can be anything that converts to JSObject*.
//
// This can't be used with Window, EventTarget, or Location as the "Interface"
// argument (and will fail a static_assert if you try to do that).  Use
// UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT to unwrap to those interfaces.
#define UNWRAP_OBJECT(Interface, obj, value)                        \
  mozilla::dom::binding_detail::UnwrapObjectWithCrossOriginAsserts< \
      mozilla::dom::prototypes::id::Interface,                      \
      mozilla::dom::Interface##_Binding::NativeType>(obj, value)

// UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT is just like UNWRAP_OBJECT but requires a
// JSContext in a Realm that represents "who is doing the unwrapping?" to
// properly unwrap the object.
#define UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Interface, obj, value, cx)          \
  mozilla::dom::UnwrapObject<mozilla::dom::prototypes::id::Interface,        \
                             mozilla::dom::Interface##_Binding::NativeType>( \
      obj, value, cx)

// Test whether the given object is an instance of the given interface.
#define IS_INSTANCE_OF(Interface, obj)                                       \
  mozilla::dom::IsInstanceOf<mozilla::dom::prototypes::id::Interface,        \
                             mozilla::dom::Interface##_Binding::NativeType>( \
      obj)

// Unwrap the given non-wrapper object.  This can be used with any obj that
// converts to JSObject*; as long as that JSObject* is live the return value
// will be valid.
#define UNWRAP_NON_WRAPPER_OBJECT(Interface, obj, value) \
  mozilla::dom::UnwrapNonWrapperObject<                  \
      mozilla::dom::prototypes::id::Interface,           \
      mozilla::dom::Interface##_Binding::NativeType>(obj, value)

// Some callers don't want to set an exception when unwrapping fails
// (for example, overload resolution uses unwrapping to tell what sort
// of thing it's looking at).
// U must be something that a T* can be assigned to (e.g. T* or an RefPtr<T>).
//
// The obj argument will be mutated to point to CheckedUnwrap of itself if the
// passed-in value is not a DOM object and CheckedUnwrap succeeds.
//
// If mayBeWrapper is true, there are three valid ways to invoke
// UnwrapObjectInternal: Either obj needs to be a class wrapping a
// MutableHandle<JSObject*>, with an assignment operator that sets the handle to
// the given object, or U needs to be a strong-reference smart pointer type
// (OwningNonNull or RefPtr or nsCOMPtr), or the value being stored in "value"
// must not escape past being tested for falsiness immediately after the
// UnwrapObjectInternal call.
//
// If mayBeWrapper is false, obj can just be a JSObject*, and U anything that a
// T* can be assigned to.
//
// The cx arg is in practice allowed to be either nullptr or JSContext* or a
// BindingCallContext reference.  If it's nullptr we will do a
// CheckedUnwrapStatic and it's the caller's responsibility to make sure they're
// not trying to work with Window or Location objects.  Otherwise we'll do a
// CheckedUnwrapDynamic.  This all only matters if mayBeWrapper is true; if it's
// false just pass nullptr for the cx arg.
namespace binding_detail {
template <class T, bool mayBeWrapper, typename U, typename V, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObjectInternal(V& obj, U& value,
                                                prototypes::ID protoID,
                                                uint32_t protoDepth,
                                                const CxType& cx) {
  static_assert(std::is_same_v<CxType, JSContext*> ||
                    std::is_same_v<CxType, BindingCallContext> ||
                    std::is_same_v<CxType, decltype(nullptr)>,
                "Unexpected CxType");

  /* First check to see whether we have a DOM object */
  const DOMJSClass* domClass = GetDOMClass(obj);
  if (domClass) {
    /* This object is a DOM object.  Double-check that it is safely
       castable to T by checking whether it claims to inherit from the
       class identified by protoID. */
    if (domClass->mInterfaceChain[protoDepth] == protoID) {
      value = UnwrapDOMObject<T>(obj);
      return NS_OK;
    }
  }

  /* Maybe we have a security wrapper or outer window? */
  if (!mayBeWrapper || !js::IsWrapper(obj)) {
    // For non-cross-origin-accessible methods and properties, remote object
    // proxies should behave the same as opaque wrappers.
    if (IsRemoteObjectProxy(obj)) {
      return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
    }

    /* Not a DOM object, not a wrapper, just bail */
    return NS_ERROR_XPC_BAD_CONVERT_JS;
  }

  JSObject* unwrappedObj;
  if (std::is_same_v<CxType, decltype(nullptr)>) {
    unwrappedObj = js::CheckedUnwrapStatic(obj);
  } else {
    unwrappedObj =
        js::CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false);
  }
  if (!unwrappedObj) {
    return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
  }

  if (std::is_same_v<CxType, decltype(nullptr)>) {
    // We might still have a windowproxy here.  But it shouldn't matter, because
    // that's not what the caller is looking for, so we're going to fail out
    // anyway below once we do the recursive call to ourselves with wrapper
    // unwrapping disabled.
    MOZ_ASSERT(!js::IsWrapper(unwrappedObj) || js::IsWindowProxy(unwrappedObj));
  } else {
    // We shouldn't have a wrapper by now.
    MOZ_ASSERT(!js::IsWrapper(unwrappedObj));
  }

  // Recursive call is OK, because now we're using false for mayBeWrapper and
  // we never reach this code if that boolean is false, so can't keep calling
  // ourselves.
  //
  // Unwrap into a temporary pointer, because in general unwrapping into
  // something of type U might trigger GC (e.g. release the value currently
  // stored in there, with arbitrary consequences) and invalidate the
  // "unwrappedObj" pointer.
  T* tempValue = nullptr;
  nsresult rv = UnwrapObjectInternal<T, false>(unwrappedObj, tempValue, protoID,
                                               protoDepth, nullptr);
  if (NS_SUCCEEDED(rv)) {
    // Suppress a hazard related to keeping tempValue alive across
    // UnwrapObjectInternal, because the analysis can't tell that this function
    // will not GC if maybeWrapped=False and we've already gone through a level
    // of unwrapping so unwrappedObj will be !IsWrapper.
    JS::AutoSuppressGCAnalysis suppress;

    // It's very important to not update "obj" with the "unwrappedObj" value
    // until we know the unwrap has succeeded.  Otherwise, in a situation in
    // which we have an overload of object and primitive we could end up
    // converting to the primitive from the unwrappedObj, whereas we want to do
    // it from the original object.
    obj = unwrappedObj;
    // And now assign to "value"; at this point we don't care if a GC happens
    // and invalidates unwrappedObj.
    value = tempValue;
    return NS_OK;
  }

  /* It's the wrong sort of DOM object */
  return NS_ERROR_XPC_BAD_CONVERT_JS;
}

struct MutableObjectHandleWrapper {
  explicit MutableObjectHandleWrapper(JS::MutableHandle<JSObject*> aHandle)
      : mHandle(aHandle) {}

  void operator=(JSObject* aObject) {
    MOZ_ASSERT(aObject);
    mHandle.set(aObject);
  }

  operator JSObject*() const { return mHandle; }

 private:
  JS::MutableHandle<JSObject*> mHandle;
};

struct MutableValueHandleWrapper {
  explicit MutableValueHandleWrapper(JS::MutableHandle<JS::Value> aHandle)
      : mHandle(aHandle) {}

  void operator=(JSObject* aObject) {
    MOZ_ASSERT(aObject);
#ifdef ENABLE_RECORD_TUPLE
    MOZ_ASSERT(!js::gc::MaybeForwardedIsExtendedPrimitive(*aObject));
#endif
    mHandle.setObject(*aObject);
  }

  operator JSObject*() const { return &mHandle.toObject(); }

 private:
  JS::MutableHandle<JS::Value> mHandle;
};

}  // namespace binding_detail

// UnwrapObject overloads that ensure we have a MutableHandle to keep it alive.
template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::MutableHandle<JSObject*> obj,
                                        U& value, const CxType& cx) {
  binding_detail::MutableObjectHandleWrapper wrapper(obj);
  return binding_detail::UnwrapObjectInternal<T, true>(
      wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}

template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::MutableHandle<JS::Value> obj,
                                        U& value, const CxType& cx) {
  MOZ_ASSERT(obj.isObject());
  binding_detail::MutableValueHandleWrapper wrapper(obj);
  return binding_detail::UnwrapObjectInternal<T, true>(
      wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}

// UnwrapObject overloads that ensure we have a strong ref to keep it alive.
template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, RefPtr<U>& value,
                                        const CxType& cx) {
  return binding_detail::UnwrapObjectInternal<T, true>(
      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}

template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, nsCOMPtr<U>& value,
                                        const CxType& cx) {
  return binding_detail::UnwrapObjectInternal<T, true>(
      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}

template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, OwningNonNull<U>& value,
                                        const CxType& cx) {
  return binding_detail::UnwrapObjectInternal<T, true>(
      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}

template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, NonNull<U>& value,
                                        const CxType& cx) {
  return binding_detail::UnwrapObjectInternal<T, true>(
      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}

// An UnwrapObject overload that just calls one of the JSObject* ones.
template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::Handle<JS::Value> obj, U& value,
                                        const CxType& cx) {
  MOZ_ASSERT(obj.isObject());
  return UnwrapObject<PrototypeID, T>(&obj.toObject(), value, cx);
}

template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::Handle<JS::Value> obj,
                                        NonNull<U>& value, const CxType& cx) {
  MOZ_ASSERT(obj.isObject());
  return UnwrapObject<PrototypeID, T>(&obj.toObject(), value, cx);
}

template <prototypes::ID PrototypeID>
MOZ_ALWAYS_INLINE void AssertStaticUnwrapOK() {
  static_assert(PrototypeID != prototypes::id::Window,
                "Can't do static unwrap of WindowProxy; use "
                "UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a cross-origin-object "
                "aware version of IS_INSTANCE_OF");
  static_assert(PrototypeID != prototypes::id::EventTarget,
                "Can't do static unwrap of WindowProxy (which an EventTarget "
                "might be); use UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a "
                "cross-origin-object aware version of IS_INSTANCE_OF");
  static_assert(PrototypeID != prototypes::id::Location,
                "Can't do static unwrap of Location; use "
                "UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a cross-origin-object "
                "aware version of IS_INSTANCE_OF");
}

namespace binding_detail {
// This function is just here so we can do some static asserts in a centralized
// place instead of putting them in every single UnwrapObject overload.
template <prototypes::ID PrototypeID, class T, typename U, typename V>
MOZ_ALWAYS_INLINE nsresult UnwrapObjectWithCrossOriginAsserts(V&& obj,
                                                              U& value) {
  AssertStaticUnwrapOK<PrototypeID>();
  return UnwrapObject<PrototypeID, T>(obj, value, nullptr);
}
}  // namespace binding_detail

template <prototypes::ID PrototypeID, class T>
MOZ_ALWAYS_INLINE bool IsInstanceOf(JSObject* obj) {
  AssertStaticUnwrapOK<PrototypeID>();
  void* ignored;
  nsresult unwrapped = binding_detail::UnwrapObjectInternal<T, true>(
      obj, ignored, PrototypeID, PrototypeTraits<PrototypeID>::Depth, nullptr);
  return NS_SUCCEEDED(unwrapped);
}

template <prototypes::ID PrototypeID, class T, typename U>
MOZ_ALWAYS_INLINE nsresult UnwrapNonWrapperObject(JSObject* obj, U& value) {
  MOZ_ASSERT(!js::IsWrapper(obj));
  return binding_detail::UnwrapObjectInternal<T, false>(
      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, nullptr);
}

MOZ_ALWAYS_INLINE bool IsConvertibleToDictionary(JS::Handle<JS::Value> val) {
  return val.isNullOrUndefined() || val.isObject();
}

// The items in the protoAndIfaceCache are indexed by the prototypes::id::ID,
// constructors::id::ID and namedpropertiesobjects::id::ID enums, in that order.
// The end of the prototype objects should be the start of the interface
// objects, and the end of the interface objects should be the start of the
// named properties objects.
static_assert((size_t)constructors::id::_ID_Start ==
                      (size_t)prototypes::id::_ID_Count &&
                  (size_t)namedpropertiesobjects::id::_ID_Start ==
                      (size_t)constructors::id::_ID_Count,
              "Overlapping or discontiguous indexes.");
const size_t kProtoAndIfaceCacheCount = namedpropertiesobjects::id::_ID_Count;

class ProtoAndIfaceCache {
  // The caching strategy we use depends on what sort of global we're dealing
  // with.  For a window-like global, we want everything to be as fast as
  // possible, so we use a flat array, indexed by prototype/constructor ID.
  // For everything else (e.g. globals for JSMs), space is more important than
  // speed, so we use a two-level lookup table.

  class ArrayCache
      : public Array<JS::Heap<JSObject*>, kProtoAndIfaceCacheCount> {
   public:
    bool HasEntryInSlot(size_t i) {
      // Do an explicit call to the Heap<…> bool conversion operator. Because
      // that operator is marked explicit we'd otherwise end up doing an
      // implicit cast to JSObject* first, causing an unnecessary call to
      // exposeToActiveJS().
      return bool((*this)[i]);
    }

    JS::Heap<JSObject*>& EntrySlotOrCreate(size_t i) { return (*this)[i]; }

    JS::Heap<JSObject*>& EntrySlotMustExist(size_t i) { return (*this)[i]; }

    void Trace(JSTracer* aTracer) {
      for (size_t i = 0; i < ArrayLength(*this); ++i) {
        JS::TraceEdge(aTracer, &(*this)[i], "protoAndIfaceCache[i]");
      }
    }

    size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
      return aMallocSizeOf(this);
    }
  };

  class PageTableCache {
   public:
    PageTableCache() { memset(mPages.begin(), 0, sizeof(mPages)); }

    ~PageTableCache() {
      for (size_t i = 0; i < ArrayLength(mPages); ++i) {
        delete mPages[i];
      }
    }

    bool HasEntryInSlot(size_t i) {
      MOZ_ASSERT(i < kProtoAndIfaceCacheCount);
      size_t pageIndex = i / kPageSize;
      size_t leafIndex = i % kPageSize;
      Page* p = mPages[pageIndex];
      if (!p) {
        return false;
      }
      // Do an explicit call to the Heap<…> bool conversion operator. Because
      // that operator is marked explicit we'd otherwise end up doing an
      // implicit cast to JSObject* first, causing an unnecessary call to
      // exposeToActiveJS().
      return bool((*p)[leafIndex]);
    }

    JS::Heap<JSObject*>& EntrySlotOrCreate(size_t i) {
      MOZ_ASSERT(i < kProtoAndIfaceCacheCount);
      size_t pageIndex = i / kPageSize;
      size_t leafIndex = i % kPageSize;
      Page* p = mPages[pageIndex];
      if (!p) {
        p = new Page;
        mPages[pageIndex] = p;
      }
      return (*p)[leafIndex];
    }

    JS::Heap<JSObject*>& EntrySlotMustExist(size_t i) {
      MOZ_ASSERT(i < kProtoAndIfaceCacheCount);
      size_t pageIndex = i / kPageSize;
      size_t leafIndex = i % kPageSize;
      Page* p = mPages[pageIndex];
      MOZ_ASSERT(p);
      return (*p)[leafIndex];
    }

    void Trace(JSTracer* trc) {
      for (size_t i = 0; i < ArrayLength(mPages); ++i) {
        Page* p = mPages[i];
        if (p) {
          for (size_t j = 0; j < ArrayLength(*p); ++j) {
            JS::TraceEdge(trc, &(*p)[j], "protoAndIfaceCache[i]");
          }
        }
      }
    }

    size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
      size_t n = aMallocSizeOf(this);
      for (size_t i = 0; i < ArrayLength(mPages); ++i) {
        n += aMallocSizeOf(mPages[i]);
      }
      return n;
    }

   private:
    static const size_t kPageSize = 16;
    typedef Array<JS::Heap<JSObject*>, kPageSize> Page;
    static const size_t kNPages =
        kProtoAndIfaceCacheCount / kPageSize +
        size_t(bool(kProtoAndIfaceCacheCount % kPageSize));
    Array<Page*, kNPages> mPages;
  };

 public:
  enum Kind { WindowLike, NonWindowLike };

  explicit ProtoAndIfaceCache(Kind aKind) : mKind(aKind) {
    MOZ_COUNT_CTOR(ProtoAndIfaceCache);
    if (aKind == WindowLike) {
      mArrayCache = new ArrayCache();
    } else {
      mPageTableCache = new PageTableCache();
    }
  }

  ~ProtoAndIfaceCache() {
    if (mKind == WindowLike) {
      delete mArrayCache;
    } else {
      delete mPageTableCache;
    }
    MOZ_COUNT_DTOR(ProtoAndIfaceCache);
  }

#define FORWARD_OPERATION(opName, args)    \
  do {                                     \
    if (mKind == WindowLike) {             \
      return mArrayCache->opName args;     \
    } else {                               \
      return mPageTableCache->opName args; \
    }                                      \
  } while (0)

  // Return whether slot i contains an object.  This doesn't return the object
  // itself because in practice consumers just want to know whether it's there
  // or not, and that doesn't require barriering, which returning the object
  // pointer does.
  bool HasEntryInSlot(size_t i) { FORWARD_OPERATION(HasEntryInSlot, (i)); }

  // Return a reference to slot i, creating it if necessary.  There
  // may not be an object in the returned slot.
  JS::Heap<JSObject*>& EntrySlotOrCreate(size_t i) {
    FORWARD_OPERATION(EntrySlotOrCreate, (i));
  }

  // Return a reference to slot i, which is guaranteed to already
  // exist.  There may not be an object in the slot, if prototype and
  // constructor initialization for one of our bindings failed.
  JS::Heap<JSObject*>& EntrySlotMustExist(size_t i) {
    FORWARD_OPERATION(EntrySlotMustExist, (i));
  }

  void Trace(JSTracer* aTracer) { FORWARD_OPERATION(Trace, (aTracer)); }

  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
    size_t n = aMallocSizeOf(this);
    n += (mKind == WindowLike
              ? mArrayCache->SizeOfIncludingThis(aMallocSizeOf)
              : mPageTableCache->SizeOfIncludingThis(aMallocSizeOf));
    return n;
  }
#undef FORWARD_OPERATION

 private:
  union {
    ArrayCache* mArrayCache;
    PageTableCache* mPageTableCache;
  };
  Kind mKind;
};

inline void AllocateProtoAndIfaceCache(JSObject* obj,
                                       ProtoAndIfaceCache::Kind aKind) {
  MOZ_ASSERT(JS::GetClass(obj)->flags & JSCLASS_DOM_GLOBAL);
  MOZ_ASSERT(JS::GetReservedSlot(obj, DOM_PROTOTYPE_SLOT).isUndefined());

  ProtoAndIfaceCache* protoAndIfaceCache = new ProtoAndIfaceCache(aKind);

  JS::SetReservedSlot(obj, DOM_PROTOTYPE_SLOT,
                      JS::PrivateValue(protoAndIfaceCache));
}

#ifdef DEBUG
struct VerifyTraceProtoAndIfaceCacheCalledTracer : public JS::CallbackTracer {
  bool ok;

  explicit VerifyTraceProtoAndIfaceCacheCalledTracer(JSContext* cx)
      : JS::CallbackTracer(cx, JS::TracerKind::VerifyTraceProtoAndIface),
        ok(false) {}

  void onChild(JS::GCCellPtr, const char* name) override {
    // We don't do anything here, we only want to verify that
    // TraceProtoAndIfaceCache was called.
  }
};
#endif

inline void TraceProtoAndIfaceCache(JSTracer* trc, JSObject* obj) {
  MOZ_ASSERT(JS::GetClass(obj)->flags & JSCLASS_DOM_GLOBAL);

#ifdef DEBUG
  if (trc->kind() == JS::TracerKind::VerifyTraceProtoAndIface) {
    // We don't do anything here, we only want to verify that
    // TraceProtoAndIfaceCache was called.
    static_cast<VerifyTraceProtoAndIfaceCacheCalledTracer*>(trc)->ok = true;
    return;
  }
#endif

  if (!DOMGlobalHasProtoAndIFaceCache(obj)) return;
  ProtoAndIfaceCache* protoAndIfaceCache = GetProtoAndIfaceCache(obj);
  protoAndIfaceCache->Trace(trc);
}

inline void DestroyProtoAndIfaceCache(JSObject* obj) {
  MOZ_ASSERT(JS::GetClass(obj)->flags & JSCLASS_DOM_GLOBAL);

  if (!DOMGlobalHasProtoAndIFaceCache(obj)) {
    return;
  }

  ProtoAndIfaceCache* protoAndIfaceCache = GetProtoAndIfaceCache(obj);

  delete protoAndIfaceCache;
}

/**
 * Add constants to an object.
 */
bool DefineConstants(JSContext* cx, JS::Handle<JSObject*> obj,
                     const ConstantSpec* cs);

struct JSNativeHolder {
  JSNative mNative;
  const NativePropertyHooks* mPropertyHooks;
};

struct LegacyFactoryFunction {
  const char* mName;
  const JSNativeHolder mHolder;
  unsigned mNargs;
};

// clang-format off
/*
 * Create a DOM interface object (if constructorClass is non-null) and/or a
 * DOM interface prototype object (if protoClass is non-null).
 *
 * global is used as the parent of the interface object and the interface
 *        prototype object
 * protoProto is the prototype to use for the interface prototype object.
 * interfaceProto is the prototype to use for the interface object.  This can be
 *                null if both constructorClass and constructor are null (as in,
 *                if we're not creating an interface object at all).
 * protoClass is the JSClass to use for the interface prototype object.
 *            This is null if we should not create an interface prototype
 *            object.
 * protoCache a pointer to a JSObject pointer where we should cache the
 *            interface prototype object. This must be null if protoClass is and
 *            vice versa.
 * constructorClass is the JSClass to use for the interface object.
 *                  This is null if we should not create an interface object or
 *                  if it should be a function object.
 * constructor holds the JSNative to back the interface object which should be a
 *             Function, unless constructorClass is non-null in which case it is
 *             ignored. If this is null and constructorClass is also null then
 *             we should not create an interface object at all.
 * ctorNargs is the length of the constructor function; 0 if no constructor
 * isConstructorChromeOnly if true, the constructor is ChromeOnly.
 * constructorCache a pointer to a JSObject pointer where we should cache the
 *                  interface object. This must be null if both constructorClass
 *                  and constructor are null, and non-null otherwise.
 * properties contains the methods, attributes and constants to be defined on
 *            objects in any compartment.
 * chromeProperties contains the methods, attributes and constants to be defined
 *                  on objects in chrome compartments. This must be null if the
 *                  interface doesn't have any ChromeOnly properties or if the
 *                  object is being created in non-chrome compartment.
 * name the name to use for 1) the WebIDL class string, which is the value
 *      that's used for @@toStringTag, 2) the name property for interface
 *      objects and 3) the property on the global object that would be set to
 *      the interface object. In general this is the interface identifier.
 *      LegacyNamespace would expect something different for 1), but we don't
 *      support that. The class string for default iterator objects is not
 *      usable as 2) or 3), but default iterator objects don't have an interface
 *      object.
 * defineOnGlobal controls whether properties should be defined on the given
 *                global for the interface object (if any) and named
 *                constructors (if any) for this interface.  This can be
 *                false in situations where we want the properties to only
 *                appear on privileged Xrays but not on the unprivileged
 *                underlying global.
 * unscopableNames if not null it points to a null-terminated list of const
 *                 char* names of the unscopable properties for this interface.
 * isGlobal if true, we're creating interface objects for a [Global] interface,
 *          and hence shouldn't define properties on the prototype object.
 * legacyWindowAliases if not null it points to a null-terminated list of const
 *                     char* names of the legacy window aliases for this
 *                     interface.
 *
 * At least one of protoClass, constructorClass or constructor should be
 * non-null. If constructorClass or constructor are non-null, the resulting
 * interface object will be defined on the given global with property name
 * |name|, which must also be non-null.
 */
// clang-format on
void CreateInterfaceObjects(
    JSContext* cx, JS::Handle<JSObject*> global,
    JS::Handle<JSObject*> protoProto, const DOMIfaceAndProtoJSClass* protoClass,
    JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto,
    const DOMIfaceJSClass* constructorClass, unsigned ctorNargs,
    bool isConstructorChromeOnly,
    const LegacyFactoryFunction* legacyFactoryFunctions,
    JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties,
    const NativeProperties* chromeOnlyProperties, const char* name,
    bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal,
    const char* const* legacyWindowAliases, bool isNamespace);

/**
 * Define the properties (regular and chrome-only) on obj.
 *
 * obj the object to install the properties on. This should be the interface
 *     prototype object for regular interfaces and the instance object for
 *     interfaces marked with Global.
 * properties contains the methods, attributes and constants to be defined on
 *            objects in any compartment.
 * chromeProperties contains the methods, attributes and constants to be defined
 *                  on objects in chrome compartments. This must be null if the
 *                  interface doesn't have any ChromeOnly properties or if the
 *                  object is being created in non-chrome compartment.
 */
bool DefineProperties(JSContext* cx, JS::Handle<JSObject*> obj,
                      const NativeProperties* properties,
                      const NativeProperties* chromeOnlyProperties);

/*
 * Define the legacy unforgeable methods on an object.
 */
bool DefineLegacyUnforgeableMethods(
    JSContext* cx, JS::Handle<JSObject*> obj,
    const Prefable<const JSFunctionSpec>* props);

/*
 * Define the legacy unforgeable attributes on an object.
 */
bool DefineLegacyUnforgeableAttributes(
    JSContext* cx, JS::Handle<JSObject*> obj,
    const Prefable<const JSPropertySpec>* props);

#define HAS_MEMBER_TYPEDEFS \
 private:                   \
  typedef char yes[1];      \
  typedef char no[2]

#ifdef _MSC_VER
#  define HAS_MEMBER_CHECK(_name) \
    template <typename V>         \
    static yes& Check##_name(char(*)[(&V::_name == 0) + 1])
#else
#  define HAS_MEMBER_CHECK(_name) \
    template <typename V>         \
    static yes& Check##_name(char(*)[sizeof(&V::_name) + 1])
#endif

#define HAS_MEMBER(_memberName, _valueName) \
 private:                                   \
  HAS_MEMBER_CHECK(_memberName);            \
  template <typename V>                     \
  static no& Check##_memberName(...);       \
                                            \
 public:                                    \
  static bool const _valueName =            \
      sizeof(Check##_memberName<T>(nullptr)) == sizeof(yes)

template <class T>
struct NativeHasMember {
  HAS_MEMBER_TYPEDEFS;

  HAS_MEMBER(GetParentObject, GetParentObject);
  HAS_MEMBER(WrapObject, WrapObject);
};

template <class T>
struct IsSmartPtr {
  HAS_MEMBER_TYPEDEFS;

  HAS_MEMBER(get, value);
};

template <class T>
struct IsRefcounted {
  HAS_MEMBER_TYPEDEFS;

  HAS_MEMBER(AddRef, HasAddref);
  HAS_MEMBER(Release, HasRelease);

 public:
  static bool const value = HasAddref && HasRelease;

 private:
  // This struct only works if T is fully declared (not just forward declared).
  // The std::is_base_of check will ensure that, we don't really need it for any
  // other reason (the static assert will of course always be true).
  static_assert(!std::is_base_of<nsISupports, T>::value || IsRefcounted::value,
                "Classes derived from nsISupports are refcounted!");
};

#undef HAS_MEMBER
#undef HAS_MEMBER_CHECK
#undef HAS_MEMBER_TYPEDEFS

#ifdef DEBUG
template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
struct CheckWrapperCacheCast {
  static bool Check() {
    return reinterpret_cast<uintptr_t>(
               static_cast<nsWrapperCache*>(reinterpret_cast<T*>(1))) == 1;
  }
};
template <class T>
struct CheckWrapperCacheCast<T, true> {
  static bool Check() { return true; }
};
#endif

inline bool TryToOuterize(JS::MutableHandle<JS::Value> rval) {
#ifdef ENABLE_RECORD_TUPLE
  if (rval.isExtendedPrimitive()) {
    return true;
  }
#endif
  MOZ_ASSERT(rval.isObject());
  if (js::IsWindow(&rval.toObject())) {
    JSObject* obj = js::ToWindowProxyIfWindow(&rval.toObject());
    MOZ_ASSERT(obj);
    rval.set(JS::ObjectValue(*obj));
  }

  return true;
}

inline bool TryToOuterize(JS::MutableHandle<JSObject*> obj) {
  if (js::IsWindow(obj)) {
    JSObject* proxy = js::ToWindowProxyIfWindow(obj);
    MOZ_ASSERT(proxy);
    obj.set(proxy);
  }

  return true;
}

// Make sure to wrap the given string value into the right compartment, as
// needed.
MOZ_ALWAYS_INLINE
bool MaybeWrapStringValue(JSContext* cx, JS::MutableHandle<JS::Value> rval) {
  MOZ_ASSERT(rval.isString());
  JSString* str = rval.toString();
  if (JS::GetStringZone(str) != js::GetContextZone(cx)) {
    return JS_WrapValue(cx, rval);
  }
  return true;
}

// Make sure to wrap the given object value into the right compartment as
// needed.  This will work correctly, but possibly slowly, on all objects.
MOZ_ALWAYS_INLINE
bool MaybeWrapObjectValue(JSContext* cx, JS::MutableHandle<JS::Value> rval) {
  MOZ_ASSERT(rval.hasObjectPayload());

  // Cross-compartment always requires wrapping.
  JSObject* obj = &rval.getObjectPayload();
  if (JS::GetCompartment(obj) != js::GetContextCompartment(cx)) {
    return JS_WrapValue(cx, rval);
  }

  // We're same-compartment, but we might still need to outerize if we
  // have a Window.
  return TryToOuterize(rval);
}

// Like MaybeWrapObjectValue, but working with a
// JS::MutableHandle<JSObject*> which must be non-null.
MOZ_ALWAYS_INLINE
bool MaybeWrapObject(JSContext* cx, JS::MutableHandle<JSObject*> obj) {
  if (JS::GetCompartment(obj) != js::GetContextCompartment(cx)) {
    return JS_WrapObject(cx, obj);
  }

  // We're same-compartment, but we might still need to outerize if we
  // have a Window.
  return TryToOuterize(obj);
}

// Like MaybeWrapObjectValue, but also allows null
MOZ_ALWAYS_INLINE
bool MaybeWrapObjectOrNullValue(JSContext* cx,
                                JS::MutableHandle<JS::Value> rval) {
  MOZ_ASSERT(rval.isObjectOrNull());
  if (rval.isNull()) {
    return true;
  }
  return MaybeWrapObjectValue(cx, rval);
}

// Wrapping for objects that are known to not be DOM objects
MOZ_ALWAYS_INLINE
bool MaybeWrapNonDOMObjectValue(JSContext* cx,
                                JS::MutableHandle<JS::Value> rval) {
  MOZ_ASSERT(rval.isObject());
  // Compared to MaybeWrapObjectValue we just skip the TryToOuterize call.  The
  // only reason it would be needed is if we have a Window object, which would
  // have a DOM class.  Assert that we don't have any DOM-class objects coming
  // through here.
  MOZ_ASSERT(!GetDOMClass(&rval.toObject()));

  JSObject* obj = &rval.toObject();
  if (JS::GetCompartment(obj) == js::GetContextCompartment(cx)) {
    return true;
  }
  return JS_WrapValue(cx, rval);
}

// Like MaybeWrapNonDOMObjectValue but allows null
MOZ_ALWAYS_INLINE
bool MaybeWrapNonDOMObjectOrNullValue(JSContext* cx,
                                      JS::MutableHandle<JS::Value> rval) {
  MOZ_ASSERT(rval.isObjectOrNull());
  if (rval.isNull()) {
    return true;
  }
  return MaybeWrapNonDOMObjectValue(cx, rval);
}

// If rval is a gcthing and is not in the compartment of cx, wrap rval
// into the compartment of cx (typically by replacing it with an Xray or
// cross-compartment wrapper around the original object).
MOZ_ALWAYS_INLINE bool MaybeWrapValue(JSContext* cx,
                                      JS::MutableHandle<JS::Value> rval) {
  if (rval.isGCThing()) {
    if (rval.isString()) {
      return MaybeWrapStringValue(cx, rval);
    }
    if (rval.hasObjectPayload()) {
      return MaybeWrapObjectValue(cx, rval);
    }
    // This could be optimized by checking the zone first, similar to
    // the way strings are handled. At present, this is used primarily
    // for structured cloning, so avoiding the overhead of JS_WrapValue
    // calls is less important than for other types.
    if (rval.isBigInt()) {
      return JS_WrapValue(cx, rval);
    }
    MOZ_ASSERT(rval.isSymbol());
    JS_MarkCrossZoneId(cx, JS::PropertyKey::Symbol(rval.toSymbol()));
  }
  return true;
}

namespace binding_detail {
enum GetOrCreateReflectorWrapBehavior {
  eWrapIntoContextCompartment,
  eDontWrapIntoContextCompartment
};

template <class T>
struct TypeNeedsOuterization {
  // We only need to outerize Window objects, so anything inheriting from
  // nsGlobalWindow (which inherits from EventTarget itself).
  static const bool value = std::is_base_of<nsGlobalWindowInner, T>::value ||
                            std::is_base_of<nsGlobalWindowOuter, T>::value ||
                            std::is_same_v<EventTarget, T>;
};

#ifdef DEBUG
template <typename T, bool isISupports = std::is_base_of<nsISupports, T>::value>
struct CheckWrapperCacheTracing {
  static inline void Check(T* aObject) {}
};

template <typename T>
struct CheckWrapperCacheTracing<T, true> {
  static void Check(T* aObject) {
    // Rooting analysis thinks QueryInterface may GC, but we're dealing with
    // a subset of QueryInterface, C++ only types here.
    JS::AutoSuppressGCAnalysis nogc;

    nsWrapperCache* wrapperCacheFromQI = nullptr;
    aObject->QueryInterface(NS_GET_IID(nsWrapperCache),
                            reinterpret_cast<void**>(&wrapperCacheFromQI));

    MOZ_ASSERT(wrapperCacheFromQI,
               "Missing nsWrapperCache from QueryInterface implementation?");

    if (!wrapperCacheFromQI->GetWrapperPreserveColor()) {
      // Can't assert that we trace the wrapper, since we don't have any
      // wrapper to trace.
      return;
    }

    nsISupports* ccISupports = nullptr;
    aObject->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
                            reinterpret_cast<void**>(&ccISupports));
    MOZ_ASSERT(ccISupports,
               "nsWrapperCache object which isn't cycle collectable?");

    nsXPCOMCycleCollectionParticipant* participant = nullptr;
    CallQueryInterface(ccISupports, &participant);
    MOZ_ASSERT(participant, "Can't QI to CycleCollectionParticipant?");

    wrapperCacheFromQI->CheckCCWrapperTraversal(ccISupports, participant);
  }
};

void AssertReflectorHasGivenProto(JSContext* aCx, JSObject* aReflector,
                                  JS::Handle<JSObject*> aGivenProto);
#endif  // DEBUG

template <class T, GetOrCreateReflectorWrapBehavior wrapBehavior>
MOZ_ALWAYS_INLINE bool DoGetOrCreateDOMReflector(
    JSContext* cx, T* value, JS::Handle<JSObject*> givenProto,
    JS::MutableHandle<JS::Value> rval) {
  MOZ_ASSERT(value);
  MOZ_ASSERT_IF(givenProto, js::IsObjectInContextCompartment(givenProto, cx));
  JSObject* obj = value->GetWrapper();
  if (obj) {
#ifdef DEBUG
    AssertReflectorHasGivenProto(cx, obj, givenProto);
    // Have to reget obj because AssertReflectorHasGivenProto can
    // trigger gc so the pointer may now be invalid.
    obj = value->GetWrapper();
#endif
  } else {
    obj = value->WrapObject(cx, givenProto);
    if (!obj) {
      // At this point, obj is null, so just return false.
      // Callers seem to be testing JS_IsExceptionPending(cx) to
      // figure out whether WrapObject() threw.
      return false;
    }

#ifdef DEBUG
    if (std::is_base_of<nsWrapperCache, T>::value) {
      CheckWrapperCacheTracing<T>::Check(value);
    }
#endif
  }

#ifdef DEBUG
  const DOMJSClass* clasp = GetDOMClass(obj);
  // clasp can be null if the cache contained a non-DOM object.
  if (clasp) {
    // Some sanity asserts about our object.  Specifically:
    // 1)  If our class claims we're nsISupports, we better be nsISupports
    //     XXXbz ideally, we could assert that reinterpret_cast to nsISupports
    //     does the right thing, but I don't see a way to do it.  :(
    // 2)  If our class doesn't claim we're nsISupports we better be
    //     reinterpret_castable to nsWrapperCache.
    MOZ_ASSERT(clasp, "What happened here?");
    MOZ_ASSERT_IF(clasp->mDOMObjectIsISupports,
                  (std::is_base_of<nsISupports, T>::value));
    MOZ_ASSERT(CheckWrapperCacheCast<T>::Check());
  }
#endif

#ifdef ENABLE_RECORD_TUPLE
  MOZ_ASSERT(!js::gc::MaybeForwardedIsExtendedPrimitive(*obj));
#endif
  rval.set(JS::ObjectValue(*obj));

  if (JS::GetCompartment(obj) == js::GetContextCompartment(cx)) {
    return TypeNeedsOuterization<T>::value ? TryToOuterize(rval) : true;
  }

  if (wrapBehavior == eDontWrapIntoContextCompartment) {
    if (TypeNeedsOuterization<T>::value) {
      JSAutoRealm ar(cx, obj);
      return TryToOuterize(rval);
    }

    return true;
  }

  return JS_WrapValue(cx, rval);
}

}  // namespace binding_detail

// Create a JSObject wrapping "value", if there isn't one already, and store it
// in rval.  "value" must be a concrete class that implements a
// GetWrapperPreserveColor() which can return its existing wrapper, if any, and
// a WrapObject() which will try to create a wrapper. Typically, this is done by
// having "value" inherit from nsWrapperCache.
//
// The value stored in rval will be ready to be exposed to whatever JS
// is running on cx right now.  In particular, it will be in the
// compartment of cx, and outerized as needed.
template <class T>
MOZ_ALWAYS_INLINE bool GetOrCreateDOMReflector(
    JSContext* cx, T* value, JS::MutableHandle<JS::Value> rval,
    JS::Handle<JSObject*> givenProto = nullptr) {
  using namespace binding_detail;
  return DoGetOrCreateDOMReflector<T, eWrapIntoContextCompartment>(
      cx, value, givenProto, rval);
}

// Like GetOrCreateDOMReflector but doesn't wrap into the context compartment,
// and hence does not actually require cx to be in a compartment.
template <class T>
MOZ_ALWAYS_INLINE bool GetOrCreateDOMReflectorNoWrap(
    JSContext* cx, T* value, JS::MutableHandle<JS::Value> rval) {
  using namespace binding_detail;
  return DoGetOrCreateDOMReflector<T, eDontWrapIntoContextCompartment>(
      cx, value, nullptr, rval);
}

// Helper for different overloadings of WrapNewBindingNonWrapperCachedObject()
inline bool FinishWrapping(JSContext* cx, JS::Handle<JSObject*> obj,
                           JS::MutableHandle<JS::Value> rval) {
#ifdef ENABLE_RECORD_TUPLE
  // If calling an (object) value's WrapObject() method returned a record/tuple,
  // then something is very wrong.
  MOZ_ASSERT(!js::gc::MaybeForwardedIsExtendedPrimitive(*obj));
#endif

  // We can end up here in all sorts of compartments, per comments in
  // WrapNewBindingNonWrapperCachedObject(). Make sure to JS_WrapValue!
  rval.set(JS::ObjectValue(*obj));
  return MaybeWrapObjectValue(cx, rval);
}

// Create a JSObject wrapping "value", for cases when "value" is a
// non-wrapper-cached object using WebIDL bindings.  "value" must implement a
// WrapObject() method taking a JSContext and a prototype (possibly null) and
// returning the resulting object via a MutableHandle<JSObject*> outparam.
template <class T>
inline bool WrapNewBindingNonWrapperCachedObject(
    JSContext* cx, JS::Handle<JSObject*> scopeArg, T* value,
    JS::MutableHandle<JS::Value> rval,
    JS::Handle<JSObject*> givenProto = nullptr) {
  static_assert(IsRefcounted<T>::value, "Don't pass owned classes in here.");
  MOZ_ASSERT(value);
  // We try to wrap in the realm of the underlying object of "scope"
  JS::Rooted<JSObject*> obj(cx);
  {
    // scope for the JSAutoRealm so that we restore the realm
    // before we call JS_WrapValue.
    Maybe<JSAutoRealm> ar;
    // Maybe<Handle> doesn't so much work, and in any case, adding
    // more Maybe (one for a Rooted and one for a Handle) adds more
    // code (and branches!) than just adding a single rooted.
    JS::Rooted<JSObject*> scope(cx, scopeArg);
    JS::Rooted<JSObject*> proto(cx, givenProto);
    if (js::IsWrapper(scope)) {
      // We are working in the Realm of cx and will be producing our reflector
      // there, so we need to succeed if that realm has access to the scope.
      scope =
          js::CheckedUnwrapDynamic(scope, cx, /* stopAtWindowProxy = */ false);
      if (!scope) return false;
      ar.emplace(cx, scope);
      if (!JS_WrapObject(cx, &proto)) {
        return false;
      }
    } else {
      // cx and scope are same-compartment, but they might still be
      // different-Realm.  Enter the Realm of scope, since that's
      // where we want to create our object.
      ar.emplace(cx, scope);
    }

    MOZ_ASSERT_IF(proto, js::IsObjectInContextCompartment(proto, cx));
    MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
    if (!value->WrapObject(cx, proto, &obj)) {
      return false;
    }
  }

  return FinishWrapping(cx, obj, rval);
}

// Create a JSObject wrapping "value", for cases when "value" is a
// non-wrapper-cached owned object using WebIDL bindings.  "value" must
// implement a WrapObject() method taking a taking a JSContext and a prototype
// (possibly null) and returning two pieces of information: the resulting object
// via a MutableHandle<JSObject*> outparam and a boolean return value that is
// true if the JSObject took ownership
template <class T>
inline bool WrapNewBindingNonWrapperCachedObject(
    JSContext* cx, JS::Handle<JSObject*> scopeArg, UniquePtr<T>& value,
    JS::MutableHandle<JS::Value> rval,
    JS::Handle<JSObject*> givenProto = nullptr) {
  static_assert(!IsRefcounted<T>::value, "Only pass owned classes in here.");
  // We do a runtime check on value, because otherwise we might in
  // fact end up wrapping a null and invoking methods on it later.
  if (!value) {
    MOZ_CRASH("Don't try to wrap null objects");
  }
  // We try to wrap in the realm of the underlying object of "scope"
  JS::Rooted<JSObject*> obj(cx);
  {
    // scope for the JSAutoRealm so that we restore the realm
    // before we call JS_WrapValue.
    Maybe<JSAutoRealm> ar;
    // Maybe<Handle> doesn't so much work, and in any case, adding
    // more Maybe (one for a Rooted and one for a Handle) adds more
    // code (and branches!) than just adding a single rooted.
    JS::Rooted<JSObject*> scope(cx, scopeArg);
    JS::Rooted<JSObject*> proto(cx, givenProto);
    if (js::IsWrapper(scope)) {
      // We are working in the Realm of cx and will be producing our reflector
      // there, so we need to succeed if that realm has access to the scope.
      scope =
          js::CheckedUnwrapDynamic(scope, cx, /* stopAtWindowProxy = */ false);
      if (!scope) return false;
      ar.emplace(cx, scope);
      if (!JS_WrapObject(cx, &proto)) {
        return false;
      }
    } else {
      // cx and scope are same-compartment, but they might still be
      // different-Realm.  Enter the Realm of scope, since that's
      // where we want to create our object.
      ar.emplace(cx, scope);
    }

    MOZ_ASSERT_IF(proto, js::IsObjectInContextCompartment(proto, cx));
    MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
    if (!value->WrapObject(cx, proto, &obj)) {
      return false;
    }

    // JS object took ownership
    Unused << value.release();
  }

  return FinishWrapping(cx, obj, rval);
}

// Helper for smart pointers (nsRefPtr/nsCOMPtr).
template <template <typename> class SmartPtr, typename T,
          typename U = std::enable_if_t<IsRefcounted<T>::value, T>,
          typename V = std::enable_if_t<IsSmartPtr<SmartPtr<T>>::value, T>>
inline bool WrapNewBindingNonWrapperCachedObject(
    JSContext* cx, JS::Handle<JSObject*> scope, const SmartPtr<T>& value,
    JS::MutableHandle<JS::Value> rval,
    JS::Handle<JSObject*> givenProto = nullptr) {
  return WrapNewBindingNonWrapperCachedObject(cx, scope, value.get(), rval,
                                              givenProto);
}

// Helper for object references (as opposed to pointers).
template <typename T, typename U = std::enable_if_t<!IsSmartPtr<T>::value, T>>
inline bool WrapNewBindingNonWrapperCachedObject(
    JSContext* cx, JS::Handle<JSObject*> scope, T& value,
    JS::MutableHandle<JS::Value> rval,
    JS::Handle<JSObject*> givenProto = nullptr) {
  return WrapNewBindingNonWrapperCachedObject(cx, scope, &value, rval,
                                              givenProto);
}

template <bool Fatal>
inline bool EnumValueNotFound(BindingCallContext& cx, JS::Handle<JSString*> str,
                              const char* type, const char* sourceDescription);

template <>
inline bool EnumValueNotFound<false>(BindingCallContext& cx,
                                     JS::Handle<JSString*> str,
                                     const char* type,
                                     const char* sourceDescription) {
  // TODO: Log a warning to the console.
  return true;
}

template <>
inline bool EnumValueNotFound<true>(BindingCallContext& cx,
                                    JS::Handle<JSString*> str, const char* type,
                                    const char* sourceDescription) {
  JS::UniqueChars deflated = JS_EncodeStringToUTF8(cx, str);
  if (!deflated) {
    return false;
  }
  return cx.ThrowErrorMessage<MSG_INVALID_ENUM_VALUE>(sourceDescription,
                                                      deflated.get(), type);
}

template <typename CharT>
inline int FindEnumStringIndexImpl(const CharT* chars, size_t length,
                                   const EnumEntry* values) {
  int i = 0;
  for (const EnumEntry* value = values; value->value; ++value, ++i) {
    if (length != value->length) {
      continue;
    }

    bool equal = true;
    const char* val = value->value;
    for (size_t j = 0; j != length; ++j) {
      if (unsigned(val[j]) != unsigned(chars[j])) {
        equal = false;
        break;
      }
    }

    if (equal) {
      return i;
    }
  }

  return -1;
}

template <bool InvalidValueFatal>
inline bool FindEnumStringIndex(BindingCallContext& cx, JS::Handle<JS::Value> v,
                                const EnumEntry* values, const char* type,
                                const char* sourceDescription, int* index) {
  // JS_StringEqualsAscii is slow as molasses, so don't use it here.
  JS::Rooted<JSString*> str(cx, JS::ToString(cx, v));
  if (!str) {
    return false;
  }

  {
    size_t length;
    JS::AutoCheckCannotGC nogc;
    if (JS::StringHasLatin1Chars(str)) {
      const JS::Latin1Char* chars =
          JS_GetLatin1StringCharsAndLength(cx, nogc, str, &length);
      if (!chars) {
        return false;
      }
      *index = FindEnumStringIndexImpl(chars, length, values);
    } else {
      const char16_t* chars =
          JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &length);
      if (!chars) {
        return false;
      }
      *index = FindEnumStringIndexImpl(chars, length, values);
    }
    if (*index >= 0) {
      return true;
    }
  }

  return EnumValueNotFound<InvalidValueFatal>(cx, str, type, sourceDescription);
}

inline nsWrapperCache* GetWrapperCache(const ParentObject& aParentObject) {
  return aParentObject.mWrapperCache;
}

template <class T>
inline T* GetParentPointer(T* aObject) {
  return aObject;
}

inline nsISupports* GetParentPointer(const ParentObject& aObject) {
  return aObject.mObject;
}

template <typename T>
inline mozilla::dom::ReflectionScope GetReflectionScope(T* aParentObject) {
  return mozilla::dom::ReflectionScope::Content;
}

inline mozilla::dom::ReflectionScope GetReflectionScope(
    const ParentObject& aParentObject) {
  return aParentObject.mReflectionScope;
}

template <class T>
inline void ClearWrapper(T* p, nsWrapperCache* cache, JSObject* obj) {
  MOZ_ASSERT(cache->GetWrapperMaybeDead() == obj ||
             (js::RuntimeIsBeingDestroyed() && !cache->GetWrapperMaybeDead()));
  cache->ClearWrapper(obj);
}

template <class T>
inline void ClearWrapper(T* p, void*, JSObject* obj) {
  // QueryInterface to nsWrapperCache can't GC, we hope.
  JS::AutoSuppressGCAnalysis nogc;

  nsWrapperCache* cache;
  CallQueryInterface(p, &cache);
  ClearWrapper(p, cache, obj);
}

template <class T>
inline void UpdateWrapper(T* p, nsWrapperCache* cache, JSObject* obj,
                          const JSObject* old) {
  JS::AutoAssertGCCallback inCallback;
  cache->UpdateWrapper(obj, old);
}

template <class T>
inline void UpdateWrapper(T* p, void*, JSObject* obj, const JSObject* old) {
  JS::AutoAssertGCCallback inCallback;
  nsWrapperCache* cache;
  CallQueryInterface(p, &cache);
  UpdateWrapper(p, cache, obj, old);
}

// Attempt to preserve the wrapper, if any, for a Paris DOM bindings object.
// Return true if we successfully preserved the wrapper, or there is no wrapper
// to preserve. In the latter case we don't need to preserve the wrapper,
// because the object can only be obtained by JS once, or they cannot be
// meaningfully owned from the native side.
//
// This operation will return false only for non-nsISupports cycle-collected
// objects, because we cannot determine if they are wrappercached or not.
bool TryPreserveWrapper(JS::Handle<JSObject*> obj);

bool HasReleasedWrapper(JS::Handle<JSObject*> obj);

// Can only be called with a DOM JSClass.
bool InstanceClassHasProtoAtDepth(const JSClass* clasp, uint32_t protoID,
                                  uint32_t depth);

// Only set allowNativeWrapper to false if you really know you need it; if in
// doubt use true. Setting it to false disables security wrappers.
bool XPCOMObjectToJsval(JSContext* cx, JS::Handle<JSObject*> scope,
                        xpcObjectHelper& helper, const nsIID* iid,
                        bool allowNativeWrapper,
                        JS::MutableHandle<JS::Value> rval);

// Special-cased wrapping for variants
bool VariantToJsval(JSContext* aCx, nsIVariant* aVariant,
                    JS::MutableHandle<JS::Value> aRetval);

// Wrap an object "p" which is not using WebIDL bindings yet.  This _will_
// actually work on WebIDL binding objects that are wrappercached, but will be
// much slower than GetOrCreateDOMReflector.  "cache" must either be null or be
// the nsWrapperCache for "p".
template <class T>
inline bool WrapObject(JSContext* cx, T* p, nsWrapperCache* cache,
                       const nsIID* iid, JS::MutableHandle<JS::Value> rval) {
  if (xpc_FastGetCachedWrapper(cx, cache, rval)) return true;
  xpcObjectHelper helper(ToSupports(p), cache);
  JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
  return XPCOMObjectToJsval(cx, scope, helper, iid, true, rval);
}

// A specialization of the above for nsIVariant, because that needs to
// do something different.
template <>
inline bool WrapObject<nsIVariant>(JSContext* cx, nsIVariant* p,
                                   nsWrapperCache* cache, const nsIID* iid,
                                   JS::MutableHandle<JS::Value> rval) {
  MOZ_ASSERT(iid);
  MOZ_ASSERT(iid->Equals(NS_GET_IID(nsIVariant)));
  return VariantToJsval(cx, p, rval);
}

// Wrap an object "p" which is not using WebIDL bindings yet.  Just like the
// variant that takes an nsWrapperCache above, but will try to auto-derive the
// nsWrapperCache* from "p".
template <class T>
inline bool WrapObject(JSContext* cx, T* p, const nsIID* iid,
                       JS::MutableHandle<JS::Value> rval) {
  return WrapObject(cx, p, GetWrapperCache(p), iid, rval);
}

// Just like the WrapObject above, but without requiring you to pick which
// interface you're wrapping as.  This should only be used for objects that have
// classinfo, for which it doesn't matter what IID is used to wrap.
template <class T>
inline bool WrapObject(JSContext* cx, T* p, JS::MutableHandle<JS::Value> rval) {
  return WrapObject(cx, p, nullptr, rval);
}

// Helper to make it possible to wrap directly out of an nsCOMPtr
template <class T>
inline bool WrapObject(JSContext* cx, const nsCOMPtr<T>& p, const nsIID* iid,
                       JS::MutableHandle<JS::Value> rval) {
  return WrapObject(cx, p.get(), iid, rval);
}

// Helper to make it possible to wrap directly out of an nsCOMPtr
template <class T>
inline bool WrapObject(JSContext* cx, const nsCOMPtr<T>& p,
                       JS::MutableHandle<JS::Value> rval) {
  return WrapObject(cx, p, nullptr, rval);
}

// Helper to make it possible to wrap directly out of an nsRefPtr
template <class T>
inline bool WrapObject(JSContext* cx, const RefPtr<T>& p, const nsIID* iid,
                       JS::MutableHandle<JS::Value> rval) {
  return WrapObject(cx, p.get(), iid, rval);
}

// Helper to make it possible to wrap directly out of an nsRefPtr
template <class T>
inline bool WrapObject(JSContext* cx, const RefPtr<T>& p,
                       JS::MutableHandle<JS::Value> rval) {
  return WrapObject(cx, p, nullptr, rval);
}

// Specialization to make it easy to use WrapObject in codegen.
template <>
inline bool WrapObject<JSObject>(JSContext* cx, JSObject* p,
                                 JS::MutableHandle<JS::Value> rval) {
  rval.set(JS::ObjectOrNullValue(p));
  return true;
}

inline bool WrapObject(JSContext* cx, JSObject& p,
                       JS::MutableHandle<JS::Value> rval) {
  rval.set(JS::ObjectValue(p));
  return true;
}

bool WrapObject(JSContext* cx, const WindowProxyHolder& p,
                JS::MutableHandle<JS::Value> rval);

// Given an object "p" that inherits from nsISupports, wrap it and return the
// result.  Null is returned on wrapping failure.  This is somewhat similar to
// WrapObject() above, but does NOT allow Xrays around the result, since we
// don't want those for our parent object.
template <typename T>
static inline JSObject* WrapNativeISupports(JSContext* cx, T* p,
                                            nsWrapperCache* cache) {
  JS::Rooted<JSObject*> retval(cx);
  {
    xpcObjectHelper helper(ToSupports(p), cache);
    JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
    JS::Rooted<JS::Value> v(cx);
    retval = XPCOMObjectToJsval(cx, scope, helper, nullptr, false, &v)
                 ? v.toObjectOrNull()
                 : nullptr;
  }
  return retval;
}

// Wrapping of our native parent, for cases when it's a WebIDL object.
template <typename T, bool hasWrapObject = NativeHasMember<T>::WrapObject>
struct WrapNativeHelper {
  static inline JSObject* Wrap(JSContext* cx, T* parent,
                               nsWrapperCache* cache) {
    MOZ_ASSERT(cache);

    JSObject* obj;
    if ((obj = cache->GetWrapper())) {
      // GetWrapper always unmarks gray.
      JS::AssertObjectIsNotGray(obj);
      return obj;
    }

    // WrapObject never returns a gray thing.
    obj = parent->WrapObject(cx, nullptr);
    JS::AssertObjectIsNotGray(obj);

    return obj;
  }
};

// Wrapping of our native parent, for cases when it's not a WebIDL object.  In
// this case it must be nsISupports.
template <typename T>
struct WrapNativeHelper<T, false> {
  static inline JSObject* Wrap(JSContext* cx, T* parent,
                               nsWrapperCache* cache) {
    JSObject* obj;
    if (cache && (obj = cache->GetWrapper())) {
#ifdef DEBUG
      JS::Rooted<JSObject*> rootedObj(cx, obj);
      NS_ASSERTION(WrapNativeISupports(cx, parent, cache) == rootedObj,
                   "Unexpected object in nsWrapperCache");
      obj = rootedObj;
#endif
      JS::AssertObjectIsNotGray(obj);
      return obj;
    }

    obj = WrapNativeISupports(cx, parent, cache);
    JS::AssertObjectIsNotGray(obj);
    return obj;
  }
};

// Finding the associated global for an object.
template <typename T>
static inline JSObject* FindAssociatedGlobal(
    JSContext* cx, T* p, nsWrapperCache* cache,
    mozilla::dom::ReflectionScope scope =
        mozilla::dom::ReflectionScope::Content) {
  if (!p) {
    return JS::CurrentGlobalOrNull(cx);
  }

  JSObject* obj = WrapNativeHelper<T>::Wrap(cx, p, cache);
  if (!obj) {
    return nullptr;
  }
  JS::AssertObjectIsNotGray(obj);

  // The object is never a CCW but it may not be in the current compartment of
  // the JSContext.
  obj = JS::GetNonCCWObjectGlobal(obj);

  switch (scope) {
    case mozilla::dom::ReflectionScope::NAC: {
      return xpc::NACScope(obj);
    }

    case mozilla::dom::ReflectionScope::UAWidget: {
      // If scope is set to UAWidgetScope, it means that the canonical reflector
      // for this native object should live in the UA widget scope.
      if (xpc::IsInUAWidgetScope(obj)) {
        return obj;
      }
      JS::Rooted<JSObject*> rootedObj(cx, obj);
      JSObject* uaWidgetScope = xpc::GetUAWidgetScope(cx, rootedObj);
      MOZ_ASSERT_IF(uaWidgetScope, JS_IsGlobalObject(uaWidgetScope));
      JS::AssertObjectIsNotGray(uaWidgetScope);
      return uaWidgetScope;
    }

    case ReflectionScope::Content:
      return obj;
  }

  MOZ_CRASH("Unknown ReflectionScope variant");

  return nullptr;
}

// Finding of the associated global for an object, when we don't want to
// explicitly pass in things like the nsWrapperCache for it.
template <typename T>
static inline JSObject* FindAssociatedGlobal(JSContext* cx, const T& p) {
  return FindAssociatedGlobal(cx, GetParentPointer(p), GetWrapperCache(p),
                              GetReflectionScope(p));
}

// Specialization for the case of nsIGlobalObject, since in that case
// we can just get the JSObject* directly.
template <>
inline JSObject* FindAssociatedGlobal(JSContext* cx,
                                      nsIGlobalObject* const& p) {
  if (!p) {
    return JS::CurrentGlobalOrNull(cx);
  }

  JSObject* global = p->GetGlobalJSObject();
  if (!global) {
    // nsIGlobalObject doesn't have a JS object anymore,
    // fallback to the current global.
    return JS::CurrentGlobalOrNull(cx);
  }

  MOZ_ASSERT(JS_IsGlobalObject(global));
  JS::AssertObjectIsNotGray(global);
  return global;
}

template <typename T,
          bool hasAssociatedGlobal = NativeHasMember<T>::GetParentObject>
struct FindAssociatedGlobalForNative {
  static JSObject* Get(JSContext* cx, JS::Handle<JSObject*> obj) {
    MOZ_ASSERT(js::IsObjectInContextCompartment(obj, cx));
    T* native = UnwrapDOMObject<T>(obj);
    return FindAssociatedGlobal(cx, native->GetParentObject());
  }
};

template <typename T>
struct FindAssociatedGlobalForNative<T, false> {
  static JSObject* Get(JSContext* cx, JS::Handle<JSObject*> obj) {
    MOZ_CRASH();
    return nullptr;
  }
};

// Helper for calling GetOrCreateDOMReflector with smart pointers
// (UniquePtr/RefPtr/nsCOMPtr) or references.
template <class T, bool isSmartPtr = IsSmartPtr<T>::value>
struct GetOrCreateDOMReflectorHelper {
  static inline bool GetOrCreate(JSContext* cx, const T& value,
                                 JS::Handle<JSObject*> givenProto,
                                 JS::MutableHandle<JS::Value> rval) {
    return GetOrCreateDOMReflector(cx, value.get(), rval, givenProto);
  }
};

template <class T>
struct GetOrCreateDOMReflectorHelper<T, false> {
  static inline bool GetOrCreate(JSContext* cx, T& value,
                                 JS::Handle<JSObject*> givenProto,
                                 JS::MutableHandle<JS::Value> rval) {
    static_assert(IsRefcounted<T>::value, "Don't pass owned classes in here.");
    return GetOrCreateDOMReflector(cx, &value, rval, givenProto);
  }
};

template <class T>
inline bool GetOrCreateDOMReflector(
    JSContext* cx, T& value, JS::MutableHandle<JS::Value> rval,
    JS::Handle<JSObject*> givenProto = nullptr) {
  return GetOrCreateDOMReflectorHelper<T>::GetOrCreate(cx, value, givenProto,
                                                       rval);
}

// Helper for calling GetOrCreateDOMReflectorNoWrap with smart pointers
// (UniquePtr/RefPtr/nsCOMPtr) or references.
template <class T, bool isSmartPtr = IsSmartPtr<T>::value>
struct GetOrCreateDOMReflectorNoWrapHelper {
  static inline bool GetOrCreate(JSContext* cx, const T& value,
                                 JS::MutableHandle<JS::Value> rval) {
    return GetOrCreateDOMReflectorNoWrap(cx, value.get(), rval);
  }
};

template <class T>
struct GetOrCreateDOMReflectorNoWrapHelper<T, false> {
  static inline bool GetOrCreate(JSContext* cx, T& value,
                                 JS::MutableHandle<JS::Value> rval) {
    return GetOrCreateDOMReflectorNoWrap(cx, &value, rval);
  }
};

template <class T>
inline bool GetOrCreateDOMReflectorNoWrap(JSContext* cx, T& value,
                                          JS::MutableHandle<JS::Value> rval) {
  return GetOrCreateDOMReflectorNoWrapHelper<T>::GetOrCreate(cx, value, rval);
}

template <class T>
inline JSObject* GetCallbackFromCallbackObject(JSContext* aCx, T* aObj) {
  return aObj->Callback(aCx);
}

// Helper for getting the callback JSObject* of a smart ptr around a
// CallbackObject or a reference to a CallbackObject or something like
// that.
template <class T, bool isSmartPtr = IsSmartPtr<T>::value>
struct GetCallbackFromCallbackObjectHelper {
  static inline JSObject* Get(JSContext* aCx, const T& aObj) {
    return GetCallbackFromCallbackObject(aCx, aObj.get());
  }
};

template <class T>
struct GetCallbackFromCallbackObjectHelper<T, false> {
  static inline JSObject* Get(JSContext* aCx, T& aObj) {
    return GetCallbackFromCallbackObject(aCx, &aObj);
  }
};

template <class T>
inline JSObject* GetCallbackFromCallbackObject(JSContext* aCx, T& aObj) {
  return GetCallbackFromCallbackObjectHelper<T>::Get(aCx, aObj);
}

static inline bool AtomizeAndPinJSString(JSContext* cx, jsid& id,
                                         const char* chars) {
  if (JSString* str = ::JS_AtomizeAndPinString(cx, chars)) {
    id = JS::PropertyKey::fromPinnedString(str);
    return true;
  }
  return false;
}

void GetInterfaceImpl(JSContext* aCx, nsIInterfaceRequestor* aRequestor,
                      nsWrapperCache* aCache, JS::Handle<JS::Value> aIID,
                      JS::MutableHandle<JS::Value> aRetval,
                      ErrorResult& aError);

template <class T>
void GetInterface(JSContext* aCx, T* aThis, JS::Handle<JS::Value> aIID,
                  JS::MutableHandle<JS::Value> aRetval, ErrorResult& aError) {
  GetInterfaceImpl(aCx, aThis, aThis, aIID, aRetval, aError);
}

bool ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp);

bool ThrowConstructorWithoutNew(JSContext* cx, const char* name);

// Helper for throwing an "invalid this" exception.
bool ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
                      bool aSecurityError, prototypes::ID aProtoId);

bool GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
                            JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
                            bool* found, JS::MutableHandle<JS::Value> vp);

//
bool HasPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
                            JS::Handle<jsid> id, bool* has);

// Append the property names in "names" to "props". If
// shadowPrototypeProperties is false then skip properties that are also
// present on the proto chain of proxy.  If shadowPrototypeProperties is true,
// then the "proxy" argument is ignored.
bool AppendNamedPropertyIds(JSContext* cx, JS::Handle<JSObject*> proxy,
                            nsTArray<nsString>& names,
                            bool shadowPrototypeProperties,
                            JS::MutableHandleVector<jsid> props);

enum StringificationBehavior { eStringify, eEmpty, eNull };

static inline JSString* ConvertJSValueToJSString(JSContext* cx,
                                                 JS::Handle<JS::Value> v) {
  if (MOZ_LIKELY(v.isString())) {
    return v.toString();
  }
  return JS::ToString(cx, v);
}

template <typename T>
static inline bool ConvertJSValueToString(
    JSContext* cx, JS::Handle<JS::Value> v,
    StringificationBehavior nullBehavior,
    StringificationBehavior undefinedBehavior, T& result) {
  JSString* s;
  if (v.isString()) {
    s = v.toString();
  } else {
    StringificationBehavior behavior;
    if (v.isNull()) {
      behavior = nullBehavior;
    } else if (v.isUndefined()) {
      behavior = undefinedBehavior;
    } else {
      behavior = eStringify;
    }

    if (behavior != eStringify) {
      if (behavior == eEmpty) {
        result.Truncate();
      } else {
        result.SetIsVoid(true);
      }
      return true;
    }

    s = JS::ToString(cx, v);
    if (!s) {
      return false;
    }
  }

  return AssignJSString(cx, result, s);
}

template <typename T>
static inline bool ConvertJSValueToString(
    JSContext* cx, JS::Handle<JS::Value> v,
    const char* /* unused sourceDescription */, T& result) {
  return ConvertJSValueToString(cx, v, eStringify, eStringify, result);
}

[[nodiscard]] bool NormalizeUSVString(nsAString& aString);

[[nodiscard]] bool NormalizeUSVString(
    binding_detail::FakeString<char16_t>& aString);

template <typename T>
static inline bool ConvertJSValueToUSVString(
    JSContext* cx, JS::Handle<JS::Value> v,
    const char* /* unused sourceDescription */, T& result) {
  if (!ConvertJSValueToString(cx, v, eStringify, eStringify, result)) {
    return false;
  }

  if (!NormalizeUSVString(result)) {
    JS_ReportOutOfMemory(cx);
    return false;
  }

  return true;
}

template <typename T>
inline bool ConvertIdToString(JSContext* cx, JS::Handle<JS::PropertyKey> id,
                              T& result, bool& isSymbol) {
  if (MOZ_LIKELY(id.isString())) {
    if (!AssignJSString(cx, result, id.toString())) {
      return false;
    }
  } else if (id.isSymbol()) {
    isSymbol = true;
    return true;
  } else {
    JS::Rooted<JS::Value> nameVal(cx, js::IdToValue(id));
    if (!ConvertJSValueToString(cx, nameVal, eStringify, eStringify, result)) {
      return false;
    }
  }
  isSymbol = false;
  return true;
}

bool ConvertJSValueToByteString(BindingCallContext& cx, JS::Handle<JS::Value> v,
                                bool nullable, const char* sourceDescription,
                                nsACString& result);

inline bool ConvertJSValueToByteString(BindingCallContext& cx,
                                       JS::Handle<JS::Value> v,
                                       const char* sourceDescription,
                                       nsACString& result) {
  return ConvertJSValueToByteString(cx, v, false, sourceDescription, result);
}

template <typename T>
void DoTraceSequence(JSTracer* trc, FallibleTArray<T>& seq);
template <typename T>
void DoTraceSequence(JSTracer* trc, nsTArray<T>& seq);

// Class used to trace sequences, with specializations for various
// sequence types.
template <typename T,
          bool isDictionary = std::is_base_of<DictionaryBase, T>::value,
          bool isTypedArray = std::is_base_of<AllTypedArraysBase, T>::value,
          bool isOwningUnion = std::is_base_of<AllOwningUnionBase, T>::value>
class SequenceTracer {
  explicit SequenceTracer() = delete;  // Should never be instantiated
};

// sequence<object> or sequence<object?>
template <>
class SequenceTracer<JSObject*, false, false, false> {
  explicit SequenceTracer() = delete;  // Should never be instantiated

 public:
  static void TraceSequence(JSTracer* trc, JSObject** objp, JSObject** end) {
    for (; objp != end; ++objp) {
      JS::TraceRoot(trc, objp, "sequence<object>");
    }
  }
};

// sequence<any>
template <>
class SequenceTracer<JS::Value, false, false, false> {
  explicit SequenceTracer() = delete;  // Should never be instantiated

 public:
  static void TraceSequence(JSTracer* trc, JS::Value* valp, JS::Value* end) {
    for (; valp != end; ++valp) {
      JS::TraceRoot(trc, valp, "sequence<any>");
    }
  }
};

// sequence<sequence<T>>
template <typename T>
class SequenceTracer<Sequence<T>, false, false, false> {
  explicit SequenceTracer() = delete;  // Should never be instantiated

 public:
  static void TraceSequence(JSTracer* trc, Sequence<T>* seqp,
                            Sequence<T>* end) {
    for (; seqp != end; ++seqp) {
      DoTraceSequence(trc, *seqp);
    }
  }
};

// sequence<sequence<T>> as return value
template <typename T>
class SequenceTracer<nsTArray<T>, false, false, false> {
  explicit SequenceTracer() = delete;  // Should never be instantiated

 public:
  static void TraceSequence(JSTracer* trc, nsTArray<T>* seqp,
                            nsTArray<T>* end) {
    for (; seqp != end; ++seqp) {
      DoTraceSequence(trc, *seqp);
    }
  }
};

// sequence<someDictionary>
template <typename T>
class SequenceTracer<T, true, false, false> {
  explicit SequenceTracer() = delete;  // Should never be instantiated

 public:
  static void TraceSequence(JSTracer* trc, T* dictp, T* end) {
    for (; dictp != end; ++dictp) {
      dictp->TraceDictionary(trc);
    }
  }
};

// sequence<SomeTypedArray>
template <typename T>
class SequenceTracer<T, false, true, false> {
  explicit SequenceTracer() = delete;  // Should never be instantiated

 public:
  static void TraceSequence(JSTracer* trc, T* arrayp, T* end) {
    for (; arrayp != end; ++arrayp) {
      arrayp->TraceSelf(trc);
    }
  }
};

// sequence<SomeOwningUnion>
template <typename T>
class SequenceTracer<T, false, false, true> {
  explicit SequenceTracer() = delete;  // Should never be instantiated

 public:
  static void TraceSequence(JSTracer* trc, T* arrayp, T* end) {
    for (; arrayp != end; ++arrayp) {
      arrayp->TraceUnion(trc);
    }
  }
};

// sequence<T?> with T? being a Nullable<T>
template <typename T>
class SequenceTracer<Nullable<T>, false, false, false> {
  explicit SequenceTracer() = delete;  // Should never be instantiated

 public:
  static void TraceSequence(JSTracer* trc, Nullable<T>* seqp,
                            Nullable<T>* end) {
    for (; seqp != end; ++seqp) {
      if (!seqp->IsNull()) {
        // Pretend like we actually have a length-one sequence here so
        // we can do template instantiation correctly for T.
        T& val = seqp->Value();
        T* ptr = &val;
        SequenceTracer<T>::TraceSequence(trc, ptr, ptr + 1);
      }
    }
  }
};

template <typename K, typename V>
void TraceRecord(JSTracer* trc, Record<K, V>& record) {
  for (auto& entry : record.Entries()) {
    // Act like it's a one-element sequence to leverage all that infrastructure.
    SequenceTracer<V>::TraceSequence(trc, &entry.mValue, &entry.mValue + 1);
  }
}

// sequence<record>
template <typename K, typename V>
class SequenceTracer<Record<K, V>, false, false, false> {
  explicit SequenceTracer() = delete;  // Should never be instantiated

 public:
  static void TraceSequence(JSTracer* trc, Record<K, V>* seqp,
                            Record<K, V>* end) {
    for (; seqp != end; ++seqp) {
      TraceRecord(trc, *seqp);
    }
  }
};

template <typename T>
void DoTraceSequence(JSTracer* trc, FallibleTArray<T>& seq) {
  SequenceTracer<T>::TraceSequence(trc, seq.Elements(),
                                   seq.Elements() + seq.Length());
}

template <typename T>
void DoTraceSequence(JSTracer* trc, nsTArray<T>& seq) {
  SequenceTracer<T>::TraceSequence(trc, seq.Elements(),
                                   seq.Elements() + seq.Length());
}

// Rooter class for sequences; this is what we mostly use in the codegen
template <typename T>
class MOZ_RAII SequenceRooter final : private JS::CustomAutoRooter {
 public:
  template <typename CX>
  SequenceRooter(const CX& cx, FallibleTArray<T>* aSequence)
      : JS::CustomAutoRooter(cx),
        mFallibleArray(aSequence),
        mSequenceType(eFallibleArray) {}

  template <typename CX>
  SequenceRooter(const CX& cx, nsTArray<T>* aSequence)
      : JS::CustomAutoRooter(cx),
        mInfallibleArray(aSequence),
        mSequenceType(eInfallibleArray) {}

  template <typename CX>
  SequenceRooter(const CX& cx, Nullable<nsTArray<T>>* aSequence)
      : JS::CustomAutoRooter(cx),
        mNullableArray(aSequence),
        mSequenceType(eNullableArray) {}

 private:
  enum SequenceType { eInfallibleArray, eFallibleArray, eNullableArray };

  virtual void trace(JSTracer* trc) override {
    if (mSequenceType == eFallibleArray) {
      DoTraceSequence(trc, *mFallibleArray);
    } else if (mSequenceType == eInfallibleArray) {
      DoTraceSequence(trc, *mInfallibleArray);
    } else {
      MOZ_ASSERT(mSequenceType == eNullableArray);
      if (!mNullableArray->IsNull()) {
        DoTraceSequence(trc, mNullableArray->Value());
      }
    }
  }

  union {
    nsTArray<T>* mInfallibleArray;
    FallibleTArray<T>* mFallibleArray;
    Nullable<nsTArray<T>>* mNullableArray;
  };

  SequenceType mSequenceType;
};

// Rooter class for Record; this is what we mostly use in the codegen.
template <typename K, typename V>
class MOZ_RAII RecordRooter final : private JS::CustomAutoRooter {
 public:
  template <typename CX>
  RecordRooter(const CX& cx, Record<K, V>* aRecord)
      : JS::CustomAutoRooter(cx), mRecord(aRecord), mRecordType(eRecord) {}

  template <typename CX>
  RecordRooter(const CX& cx, Nullable<Record<K, V>>* aRecord)
      : JS::CustomAutoRooter(cx),
        mNullableRecord(aRecord),
        mRecordType(eNullableRecord) {}

 private:
  enum RecordType { eRecord, eNullableRecord };

  virtual void trace(JSTracer* trc) override {
    if (mRecordType == eRecord) {
      TraceRecord(trc, *mRecord);
    } else {
      MOZ_ASSERT(mRecordType == eNullableRecord);
      if (!mNullableRecord->IsNull()) {
        TraceRecord(trc, mNullableRecord->Value());
      }
    }
  }

  union {
    Record<K, V>* mRecord;
    Nullable<Record<K, V>>* mNullableRecord;
  };

  RecordType mRecordType;
};

template <typename T>
class MOZ_RAII RootedUnion : public T, private JS::CustomAutoRooter {
 public:
  template <typename CX>
  explicit RootedUnion(const CX& cx) : T(), JS::CustomAutoRooter(cx) {}

  virtual void trace(JSTracer* trc) override { this->TraceUnion(trc); }
};

template <typename T>
class MOZ_STACK_CLASS NullableRootedUnion : public Nullable<T>,
                                            private JS::CustomAutoRooter {
 public:
  template <typename CX>
  explicit NullableRootedUnion(const CX& cx)
      : Nullable<T>(), JS::CustomAutoRooter(cx) {}

  virtual void trace(JSTracer* trc) override {
    if (!this->IsNull()) {
      this->Value().TraceUnion(trc);
    }
  }
};

inline bool AddStringToIDVector(JSContext* cx,
                                JS::MutableHandleVector<jsid> vector,
                                const char* name) {
  return vector.growBy(1) &&
         AtomizeAndPinJSString(cx, *(vector[vector.length() - 1]).address(),
                               name);
}

// We use one constructor JSNative to represent all DOM interface objects (so
// we can easily detect when we need to wrap them in an Xray wrapper). We store
// the real JSNative in the mNative member of a JSNativeHolder in the
// CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction object for a
// specific interface object. We also store the NativeProperties in the
// JSNativeHolder.
// Note that some interface objects are not yet a JSFunction but a normal
// JSObject with a DOMJSClass, those do not use these slots.

enum { CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT = 0 };

bool Constructor(JSContext* cx, unsigned argc, JS::Value* vp);

// Implementation of the bits that XrayWrapper needs

/**
 * This resolves operations, attributes and constants of the interfaces for obj.
 *
 * wrapper is the Xray JS object.
 * obj is the target object of the Xray, a binding's instance object or a
 *     interface or interface prototype object.
 */
bool XrayResolveOwnProperty(
    JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
    JS::Handle<jsid> id,
    JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc,
    bool& cacheOnHolder);

/**
 * Define a property on obj through an Xray wrapper.
 *
 * wrapper is the Xray JS object.
 * obj is the target object of the Xray, a binding's instance object or a
 *     interface or interface prototype object.
 * id and desc are the parameters for the property to be defined.
 * result is the out-parameter indicating success (read it only if
 *     this returns true and also sets *done to true).
 * done will be set to true if a property was set as a result of this call
 *      or if we want to always avoid setting this property
 *      (i.e. indexed properties on DOM objects)
 */
bool XrayDefineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
                        JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                        JS::Handle<JS::PropertyDescriptor> desc,
                        JS::ObjectOpResult& result, bool* done);

/**
 * Add to props the property keys of all indexed or named properties of obj and
 * operations, attributes and constants of the interfaces for obj.
 *
 * wrapper is the Xray JS object.
 * obj is the target object of the Xray, a binding's instance object or a
 *     interface or interface prototype object.
 * flags are JSITER_* flags.
 */
bool XrayOwnPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
                         JS::Handle<JSObject*> obj, unsigned flags,
                         JS::MutableHandleVector<jsid> props);

/**
 * Returns the prototype to use for an Xray for a DOM object, wrapped in cx's
 * compartment. This always returns the prototype that would be used for a DOM
 * object if we ignore any changes that might have been done to the prototype
 * chain by JS, the XBL code or plugins.
 *
 * cx should be in the Xray's compartment.
 * obj is the target object of the Xray, a binding's instance object or an
 *     interface or interface prototype object.
 */
inline bool XrayGetNativeProto(JSContext* cx, JS::Handle<JSObject*> obj,
                               JS::MutableHandle<JSObject*> protop) {
  JS::Rooted<JSObject*> global(cx, JS::GetNonCCWObjectGlobal(obj));
  {
    JSAutoRealm ar(cx, global);
    const DOMJSClass* domClass = GetDOMClass(obj);
    if (domClass) {
      ProtoHandleGetter protoGetter = domClass->mGetProto;
      if (protoGetter) {
        protop.set(protoGetter(cx));
      } else {
        protop.set(JS::GetRealmObjectPrototype(cx));
      }
    } else if (JS_ObjectIsFunction(obj)) {
      MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor));
      protop.set(JS::GetRealmFunctionPrototype(cx));
    } else {
      const JSClass* clasp = JS::GetClass(obj);
      MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp));
      ProtoGetter protoGetter =
          DOMIfaceAndProtoJSClass::FromJSClass(clasp)->mGetParentProto;
      protop.set(protoGetter(cx));
    }
  }

  return JS_WrapObject(cx, protop);
}

/**
 * Get the Xray expando class to use for the given DOM object.
 */
const JSClass* XrayGetExpandoClass(JSContext* cx, JS::Handle<JSObject*> obj);

/**
 * Delete a named property, if any.  Return value is false if exception thrown,
 * true otherwise.  The caller should not do any more work after calling this
 * function, because it has no way whether a deletion was performed and hence
 * opresult already has state set on it.  If callers ever need to change that,
 * add a "bool* found" argument and change the generated DeleteNamedProperty to
 * use it instead of a local variable.
 */
bool XrayDeleteNamedProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
                             JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                             JS::ObjectOpResult& opresult);

namespace binding_detail {

// Default implementations of the NativePropertyHooks' mResolveOwnProperty and
// mEnumerateOwnProperties for WebIDL bindings implemented as proxies.
bool ResolveOwnProperty(
    JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
    JS::Handle<jsid> id,
    JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc);
bool EnumerateOwnProperties(JSContext* cx, JS::Handle<JSObject*> wrapper,
                            JS::Handle<JSObject*> obj,
                            JS::MutableHandleVector<jsid> props);

}  // namespace binding_detail

/**
 * Get the object which should be used to cache the return value of a property
 * getter in the case of a [Cached] or [StoreInSlot] property.  `obj` is the
 * `this` value for our property getter that we're working with.
 *
 * This function can return null on failure to allocate the object, throwing on
 * the JSContext in the process.
 *
 * The isXray outparam will be set to true if obj is an Xray and false
 * otherwise.
 *
 * Note that the Slow version should only be called from
 * GetCachedSlotStorageObject.
 */
JSObject* GetCachedSlotStorageObjectSlow(JSContext* cx,
                                         JS::Handle<JSObject*> obj,
                                         bool* isXray);

inline JSObject* GetCachedSlotStorageObject(JSContext* cx,
                                            JS::Handle<JSObject*> obj,
                                            bool* isXray) {
  if (IsDOMObject(obj)) {
    *isXray = false;
    return obj;
  }

  return GetCachedSlotStorageObjectSlow(cx, obj, isXray);
}

extern NativePropertyHooks sEmptyNativePropertyHooks;

extern const JSClassOps sBoringInterfaceObjectClassClassOps;

extern const js::ObjectOps sInterfaceObjectClassObjectOps;

inline bool UseDOMXray(JSObject* obj) {
  const JSClass* clasp = JS::GetClass(obj);
  return IsDOMClass(clasp) || JS_IsNativeFunction(obj, Constructor) ||
         IsDOMIfaceAndProtoClass(clasp);
}

inline bool IsDOMConstructor(JSObject* obj) {
  if (JS_IsNativeFunction(obj, dom::Constructor)) {
    // LegacyFactoryFunction, like Image
    return true;
  }

  const JSClass* clasp = JS::GetClass(obj);
  // Check for a DOM interface object.
  return dom::IsDOMIfaceAndProtoClass(clasp) &&
         dom::DOMIfaceAndProtoJSClass::FromJSClass(clasp)->mType ==
             dom::eInterface;
}

#ifdef DEBUG
inline bool HasConstructor(JSObject* obj) {
  return JS_IsNativeFunction(obj, Constructor) ||
         JS::GetClass(obj)->getConstruct();
}
#endif

// Helpers for creating a const version of a type.
template <typename T>
const T& Constify(T& arg) {
  return arg;
}

// Helper for turning (Owning)NonNull<T> into T&
template <typename T>
T& NonNullHelper(T& aArg) {
  return aArg;
}

template <typename T>
T& NonNullHelper(NonNull<T>& aArg) {
  return aArg;
}

template <typename T>
const T& NonNullHelper(const NonNull<T>& aArg) {
  return aArg;
}

template <typename T>
T& NonNullHelper(OwningNonNull<T>& aArg) {
  return aArg;
}

template <typename T>
const T& NonNullHelper(const OwningNonNull<T>& aArg) {
  return aArg;
}

template <typename CharT>
inline void NonNullHelper(NonNull<binding_detail::FakeString<CharT>>& aArg) {
  // This overload is here to make sure that we never end up applying
  // NonNullHelper to a NonNull<binding_detail::FakeString>. If we
  // try to, it should fail to compile, since presumably the caller will try to
  // use our nonexistent return value.
}

template <typename CharT>
inline void NonNullHelper(
    const NonNull<binding_detail::FakeString<CharT>>& aArg) {
  // This overload is here to make sure that we never end up applying
  // NonNullHelper to a NonNull<binding_detail::FakeString>. If we
  // try to, it should fail to compile, since presumably the caller will try to
  // use our nonexistent return value.
}

template <typename CharT>
inline void NonNullHelper(binding_detail::FakeString<CharT>& aArg) {
  // This overload is here to make sure that we never end up applying
  // NonNullHelper to a FakeString before we've constified it.  If we
  // try to, it should fail to compile, since presumably the caller will try to
  // use our nonexistent return value.
}

template <typename CharT>
MOZ_ALWAYS_INLINE const nsTSubstring<CharT>& NonNullHelper(
    const binding_detail::FakeString<CharT>& aArg) {
  return aArg;
}

// Given a DOM reflector aObj, give its underlying DOM object a reflector in
// whatever global that underlying DOM object now thinks it should be in.  If
// this is in a different compartment from aObj, aObj will become a
// cross-compatment wrapper for the new object.  Otherwise, aObj will become the
// new object (via a brain transplant).  If the new global is the same as the
// old global, we just keep using the same object.
//
// On entry to this method, aCx and aObj must be same-compartment.
void UpdateReflectorGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
                           ErrorResult& aError);

/**
 * Used to implement the Symbol.hasInstance property of an interface object.
 */
bool InterfaceHasInstance(JSContext* cx, unsigned argc, JS::Value* vp);

bool InterfaceHasInstance(JSContext* cx, int prototypeID, int depth,
                          JS::Handle<JSObject*> instance, bool* bp);

// Used to implement the cross-context <Interface>.isInstance static method.
bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp);

// Helper for lenient getters/setters to report to console.  If this
// returns false, we couldn't even get a global.
bool ReportLenientThisUnwrappingFailure(JSContext* cx, JSObject* obj);

// Given a JSObject* that represents the chrome side of a JS-implemented WebIDL
// interface, get the nsIGlobalObject corresponding to the content side, if any.
// A false return means an exception was thrown.
bool GetContentGlobalForJSImplementedObject(BindingCallContext& cx,
                                            JS::Handle<JSObject*> obj,
                                            nsIGlobalObject** global);

void ConstructJSImplementation(const char* aContractId,
                               nsIGlobalObject* aGlobal,
                               JS::MutableHandle<JSObject*> aObject,
                               ErrorResult& aRv);

// XXX Avoid pulling in the whole ScriptSettings.h, however there should be a
// unique declaration of this function somewhere else.
JS::RootingContext* RootingCx();

template <typename T>
already_AddRefed<T> ConstructJSImplementation(const char* aContractId,
                                              nsIGlobalObject* aGlobal,
                                              ErrorResult& aRv) {
  JS::RootingContext* cx = RootingCx();
  JS::Rooted<JSObject*> jsImplObj(cx);
  ConstructJSImplementation(aContractId, aGlobal, &jsImplObj, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  MOZ_RELEASE_ASSERT(!js::IsWrapper(jsImplObj));
  JS::Rooted<JSObject*> jsImplGlobal(cx, JS::GetNonCCWObjectGlobal(jsImplObj));
  RefPtr<T> newObj = new T(jsImplObj, jsImplGlobal, aGlobal);
  return newObj.forget();
}

template <typename T>
already_AddRefed<T> ConstructJSImplementation(const char* aContractId,
                                              const GlobalObject& aGlobal,
                                              ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  if (!global) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  return ConstructJSImplementation<T>(aContractId, global, aRv);
}

/**
 * Convert an nsCString to jsval, returning true on success.
 * These functions are intended for ByteString implementations.
 * As such, the string is not UTF-8 encoded.  Any UTF8 strings passed to these
 * methods will be mangled.
 */
bool NonVoidByteStringToJsval(JSContext* cx, const nsACString& str,
                              JS::MutableHandle<JS::Value> rval);
inline bool ByteStringToJsval(JSContext* cx, const nsACString& str,
                              JS::MutableHandle<JS::Value> rval) {
  if (str.IsVoid()) {
    rval.setNull();
    return true;
  }
  return NonVoidByteStringToJsval(cx, str, rval);
}

// Convert an utf-8 encoded nsCString to jsval, returning true on success.
//
// TODO(bug 1606957): This could probably be better.
inline bool NonVoidUTF8StringToJsval(JSContext* cx, const nsACString& str,
                                     JS::MutableHandle<JS::Value> rval) {
  JSString* jsStr =
      JS_NewStringCopyUTF8N(cx, {str.BeginReading(), str.Length()});
  if (!jsStr) {
    return false;
  }
  rval.setString(jsStr);
  return true;
}

inline bool UTF8StringToJsval(JSContext* cx, const nsACString& str,
                              JS::MutableHandle<JS::Value> rval) {
  if (str.IsVoid()) {
    rval.setNull();
    return true;
  }
  return NonVoidUTF8StringToJsval(cx, str, rval);
}

template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
struct PreserveWrapperHelper {
  static void PreserveWrapper(T* aObject) {
    aObject->PreserveWrapper(aObject, NS_CYCLE_COLLECTION_PARTICIPANT(T));
  }
};

template <class T>
struct PreserveWrapperHelper<T, true> {
  static void PreserveWrapper(T* aObject) {
    aObject->PreserveWrapper(reinterpret_cast<nsISupports*>(aObject));
  }
};

template <class T>
void PreserveWrapper(T* aObject) {
  PreserveWrapperHelper<T>::PreserveWrapper(aObject);
}

template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
struct CastingAssertions {
  static bool ToSupportsIsCorrect(T*) { return true; }
  static bool ToSupportsIsOnPrimaryInheritanceChain(T*, nsWrapperCache*) {
    return true;
  }
};

template <class T>
struct CastingAssertions<T, true> {
  static bool ToSupportsIsCorrect(T* aObject) {
    return ToSupports(aObject) == reinterpret_cast<nsISupports*>(aObject);
  }
  static bool ToSupportsIsOnPrimaryInheritanceChain(T* aObject,
                                                    nsWrapperCache* aCache) {
    return reinterpret_cast<void*>(aObject) != aCache;
  }
};

template <class T>
bool ToSupportsIsCorrect(T* aObject) {
  return CastingAssertions<T>::ToSupportsIsCorrect(aObject);
}

template <class T>
bool ToSupportsIsOnPrimaryInheritanceChain(T* aObject, nsWrapperCache* aCache) {
  return CastingAssertions<T>::ToSupportsIsOnPrimaryInheritanceChain(aObject,
                                                                     aCache);
}

// Get the size of allocated memory to associate with a binding JSObject for a
// native object. This is supplied to the JS engine to allow it to schedule GC
// when necessary.
//
// This function supplies a default value and is overloaded for specific native
// object types.
inline size_t BindingJSObjectMallocBytes(void* aNativePtr) { return 0; }

// The BindingJSObjectCreator class is supposed to be used by a caller that
// wants to create and initialise a binding JSObject. After initialisation has
// been successfully completed it should call InitializationSucceeded().
// The BindingJSObjectCreator object will root the JSObject until
// InitializationSucceeded() is called on it. If the native object for the
// binding is refcounted it will also hold a strong reference to it, that
// reference is transferred to the JSObject (which holds the native in a slot)
// when InitializationSucceeded() is called. If the BindingJSObjectCreator
// object is destroyed and InitializationSucceeded() was never called on it then
// the JSObject's slot holding the native will be set to undefined, and for a
// refcounted native the strong reference will be released.
template <class T>
class MOZ_STACK_CLASS BindingJSObjectCreator {
 public:
  explicit BindingJSObjectCreator(JSContext* aCx) : mReflector(aCx) {}

  ~BindingJSObjectCreator() {
    if (mReflector) {
      JS::SetReservedSlot(mReflector, DOM_OBJECT_SLOT, JS::UndefinedValue());
    }
  }

  void CreateProxyObject(JSContext* aCx, const JSClass* aClass,
                         const DOMProxyHandler* aHandler,
                         JS::Handle<JSObject*> aProto, bool aLazyProto,
                         T* aNative, JS::Handle<JS::Value> aExpandoValue,
                         JS::MutableHandle<JSObject*> aReflector) {
    js::ProxyOptions options;
    options.setClass(aClass);
    options.setLazyProto(aLazyProto);

    aReflector.set(
        js::NewProxyObject(aCx, aHandler, aExpandoValue, aProto, options));
    if (aReflector) {
      js::SetProxyReservedSlot(aReflector, DOM_OBJECT_SLOT,
                               JS::PrivateValue(aNative));
      mNative = aNative;
      mReflector = aReflector;

      if (size_t mallocBytes = BindingJSObjectMallocBytes(aNative)) {
        JS::AddAssociatedMemory(aReflector, mallocBytes,
                                JS::MemoryUse::DOMBinding);
      }
    }
  }

  void CreateObject(JSContext* aCx, const JSClass* aClass,
                    JS::Handle<JSObject*> aProto, T* aNative,
                    JS::MutableHandle<JSObject*> aReflector) {
    aReflector.set(JS_NewObjectWithGivenProto(aCx, aClass, aProto));
    if (aReflector) {
      JS::SetReservedSlot(aReflector, DOM_OBJECT_SLOT,
                          JS::PrivateValue(aNative));
      mNative = aNative;
      mReflector = aReflector;

      if (size_t mallocBytes = BindingJSObjectMallocBytes(aNative)) {
        JS::AddAssociatedMemory(aReflector, mallocBytes,
                                JS::MemoryUse::DOMBinding);
      }
    }
  }

  void InitializationSucceeded() {
    T* pointer;
    mNative.forget(&pointer);
    mReflector = nullptr;
  }

 private:
  struct OwnedNative {
    // Make sure the native objects inherit from NonRefcountedDOMObject so
    // that we log their ctor and dtor.
    static_assert(std::is_base_of<NonRefcountedDOMObject, T>::value,
                  "Non-refcounted objects with DOM bindings should inherit "
                  "from NonRefcountedDOMObject.");

    OwnedNative& operator=(T* aNative) {
      mNative = aNative;
      return *this;
    }

    // This signature sucks, but it's the only one that will make a nsRefPtr
    // just forget about its pointer without warning.
    void forget(T** aResult) {
      *aResult = mNative;
      mNative = nullptr;
    }

    // Keep track of the pointer for use in InitializationSucceeded().
    // The caller (or, after initialization succeeds, the JS object) retains
    // ownership of the object.
    T* mNative;
  };

  JS::Rooted<JSObject*> mReflector;
  std::conditional_t<IsRefcounted<T>::value, RefPtr<T>, OwnedNative> mNative;
};

template <class T>
struct DeferredFinalizerImpl {
  using SmartPtr = std::conditional_t<
      std::is_same_v<T, nsISupports>, nsCOMPtr<T>,
      std::conditional_t<IsRefcounted<T>::value, RefPtr<T>, UniquePtr<T>>>;
  typedef SegmentedVector<SmartPtr> SmartPtrArray;

  static_assert(
      std::is_same_v<T, nsISupports> || !std::is_base_of<nsISupports, T>::value,
      "nsISupports classes should all use the nsISupports instantiation");

  static inline void AppendAndTake(
      SegmentedVector<nsCOMPtr<nsISupports>>& smartPtrArray, nsISupports* ptr) {
    smartPtrArray.InfallibleAppend(dont_AddRef(ptr));
  }
  template <class U>
  static inline void AppendAndTake(SegmentedVector<RefPtr<U>>& smartPtrArray,
                                   U* ptr) {
    smartPtrArray.InfallibleAppend(dont_AddRef(ptr));
  }
  template <class U>
  static inline void AppendAndTake(SegmentedVector<UniquePtr<U>>& smartPtrArray,
                                   U* ptr) {
    smartPtrArray.InfallibleAppend(ptr);
  }

  static void* AppendDeferredFinalizePointer(void* aData, void* aObject) {
    SmartPtrArray* pointers = static_cast<SmartPtrArray*>(aData);
    if (!pointers) {
      pointers = new SmartPtrArray();
    }
    AppendAndTake(*pointers, static_cast<T*>(aObject));
    return pointers;
  }
  static bool DeferredFinalize(uint32_t aSlice, void* aData) {
    MOZ_ASSERT(aSlice > 0, "nonsensical/useless call with aSlice == 0");
    SmartPtrArray* pointers = static_cast<SmartPtrArray*>(aData);
    uint32_t oldLen = pointers->Length();
    if (oldLen < aSlice) {
      aSlice = oldLen;
    }
    uint32_t newLen = oldLen - aSlice;
    pointers->PopLastN(aSlice);
    if (newLen == 0) {
      delete pointers;
      return true;
    }
    return false;
  }
};

template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
struct DeferredFinalizer {
  static void AddForDeferredFinalization(T* aObject) {
    typedef DeferredFinalizerImpl<T> Impl;
    DeferredFinalize(Impl::AppendDeferredFinalizePointer,
                     Impl::DeferredFinalize, aObject);
  }
};

template <class T>
struct DeferredFinalizer<T, true> {
  static void AddForDeferredFinalization(T* aObject) {
    DeferredFinalize(reinterpret_cast<nsISupports*>(aObject));
  }
};

template <class T>
static void AddForDeferredFinalization(T* aObject) {
  DeferredFinalizer<T>::AddForDeferredFinalization(aObject);
}

// This returns T's CC participant if it participates in CC and does not inherit
// from nsISupports. Otherwise, it returns null. QI should be used to get the
// participant if T inherits from nsISupports.
template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
class GetCCParticipant {
  // Helper for GetCCParticipant for classes that participate in CC.
  template <class U>
  static constexpr nsCycleCollectionParticipant* GetHelper(
      int, typename U::NS_CYCLE_COLLECTION_INNERCLASS* dummy = nullptr) {
    return T::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant();
  }
  // Helper for GetCCParticipant for classes that don't participate in CC.
  template <class U>
  static constexpr nsCycleCollectionParticipant* GetHelper(double) {
    return nullptr;
  }

 public:
  static constexpr nsCycleCollectionParticipant* Get() {
    // Passing int() here will try to call the GetHelper that takes an int as
    // its first argument. If T doesn't participate in CC then substitution for
    // the second argument (with a default value) will fail and because of
    // SFINAE the next best match (the variant taking a double) will be called.
    return GetHelper<T>(int());
  }
};

template <class T>
class GetCCParticipant<T, true> {
 public:
  static constexpr nsCycleCollectionParticipant* Get() { return nullptr; }
};

void FinalizeGlobal(JS::GCContext* aGcx, JSObject* aObj);

bool ResolveGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
                   JS::Handle<jsid> aId, bool* aResolvedp);

bool MayResolveGlobal(const JSAtomState& aNames, jsid aId, JSObject* aMaybeObj);

bool EnumerateGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
                     JS::MutableHandleVector<jsid> aProperties,
                     bool aEnumerableOnly);

struct CreateGlobalOptionsGeneric {
  static void TraceGlobal(JSTracer* aTrc, JSObject* aObj) {
    mozilla::dom::TraceProtoAndIfaceCache(aTrc, aObj);
  }
  static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal) {
    MOZ_ALWAYS_TRUE(TryPreserveWrapper(aGlobal));

    return true;
  }
};

struct CreateGlobalOptionsWithXPConnect {
  static void TraceGlobal(JSTracer* aTrc, JSObject* aObj);
  static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
};

template <class T>
using IsGlobalWithXPConnect =
    std::integral_constant<bool,
                           std::is_base_of<nsGlobalWindowInner, T>::value ||
                               std::is_base_of<MessageManagerGlobal, T>::value>;

template <class T>
struct CreateGlobalOptions
    : std::conditional_t<IsGlobalWithXPConnect<T>::value,
                         CreateGlobalOptionsWithXPConnect,
                         CreateGlobalOptionsGeneric> {
  static constexpr ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
      ProtoAndIfaceCache::NonWindowLike;
};

template <>
struct CreateGlobalOptions<nsGlobalWindowInner>
    : public CreateGlobalOptionsWithXPConnect {
  static constexpr ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
      ProtoAndIfaceCache::WindowLike;
};

uint64_t GetWindowID(void* aGlobal);
uint64_t GetWindowID(nsGlobalWindowInner* aGlobal);
uint64_t GetWindowID(DedicatedWorkerGlobalScope* aGlobal);

// The return value is true if we created and successfully performed our part of
// the setup for the global, false otherwise.
//
// Typically this method's caller will want to ensure that
// xpc::InitGlobalObjectOptions is called before, and xpc::InitGlobalObject is
// called after, this method, to ensure that this global object and its
// compartment are consistent with other global objects.
template <class T, ProtoHandleGetter GetProto>
bool CreateGlobal(JSContext* aCx, T* aNative, nsWrapperCache* aCache,
                  const JSClass* aClass, JS::RealmOptions& aOptions,
                  JSPrincipals* aPrincipal, bool aInitStandardClasses,
                  JS::MutableHandle<JSObject*> aGlobal) {
  aOptions.creationOptions()
      .setTrace(CreateGlobalOptions<T>::TraceGlobal)
      .setProfilerRealmID(GetWindowID(aNative));
  xpc::SetPrefableRealmOptions(aOptions);

  aGlobal.set(JS_NewGlobalObject(aCx, aClass, aPrincipal,
                                 JS::DontFireOnNewGlobalHook, aOptions));
  if (!aGlobal) {
    NS_WARNING("Failed to create global");
    return false;
  }

  JSAutoRealm ar(aCx, aGlobal);

  {
    JS::SetReservedSlot(aGlobal, DOM_OBJECT_SLOT, JS::PrivateValue(aNative));
    NS_ADDREF(aNative);

    aCache->SetWrapper(aGlobal);

    dom::AllocateProtoAndIfaceCache(
        aGlobal, CreateGlobalOptions<T>::ProtoAndIfaceCacheKind);

    if (!CreateGlobalOptions<T>::PostCreateGlobal(aCx, aGlobal)) {
      return false;
    }
  }

  if (aInitStandardClasses && !JS::InitRealmStandardClasses(aCx)) {
    NS_WARNING("Failed to init standard classes");
    return false;
  }

  JS::Handle<JSObject*> proto = GetProto(aCx);
  if (!proto || !JS_SetPrototype(aCx, aGlobal, proto)) {
    NS_WARNING("Failed to set proto");
    return false;
  }

  bool succeeded;
  if (!JS_SetImmutablePrototype(aCx, aGlobal, &succeeded)) {
    return false;
  }
  MOZ_ASSERT(succeeded,
             "making a fresh global object's [[Prototype]] immutable can "
             "internally fail, but it should never be unsuccessful");

  return true;
}

namespace binding_detail {
/**
 * WebIDL getters have a "generic" JSNative that is responsible for the
 * following things:
 *
 * 1) Determining the "this" pointer for the C++ call.
 * 2) Extracting the "specialized" getter from the jitinfo on the JSFunction.
 * 3) Calling the specialized getter.
 * 4) Handling exceptions as needed.
 *
 * There are several variants of (1) depending on the interface involved and
 * there are two variants of (4) depending on whether the return type is a
 * Promise.  We handle this by templating our generic getter on a
 * this-determination policy and an exception handling policy, then explicitly
 * instantiating the relevant template specializations.
 */
template <typename ThisPolicy, typename ExceptionPolicy>
bool GenericGetter(JSContext* cx, unsigned argc, JS::Value* vp);

/**
 * WebIDL setters have a "generic" JSNative that is responsible for the
 * following things:
 *
 * 1) Determining the "this" pointer for the C++ call.
 * 2) Extracting the "specialized" setter from the jitinfo on the JSFunction.
 * 3) Calling the specialized setter.
 *
 * There are several variants of (1) depending on the interface
 * involved.  We handle this by templating our generic setter on a
 * this-determination policy, then explicitly instantiating the
 * relevant template specializations.
 */
template <typename ThisPolicy>
bool GenericSetter(JSContext* cx, unsigned argc, JS::Value* vp);

/**
 * WebIDL methods have a "generic" JSNative that is responsible for the
 * following things:
 *
 * 1) Determining the "this" pointer for the C++ call.
 * 2) Extracting the "specialized" method from the jitinfo on the JSFunction.
 * 3) Calling the specialized methodx.
 * 4) Handling exceptions as needed.
 *
 * There are several variants of (1) depending on the interface involved and
 * there are two variants of (4) depending on whether the return type is a
 * Promise.  We handle this by templating our generic method on a
 * this-determination policy and an exception handling policy, then explicitly
 * instantiating the relevant template specializations.
 */
template <typename ThisPolicy, typename ExceptionPolicy>
bool GenericMethod(JSContext* cx, unsigned argc, JS::Value* vp);

// A this-extraction policy for normal getters/setters/methods.
struct NormalThisPolicy;

// A this-extraction policy for getters/setters/methods on interfaces
// that are on some global's proto chain.
struct MaybeGlobalThisPolicy;

// A this-extraction policy for lenient getters/setters.
struct LenientThisPolicy;

// A this-extraction policy for cross-origin getters/setters/methods.
struct CrossOriginThisPolicy;

// A this-extraction policy for getters/setters/methods that should
// not be allowed to be called cross-origin but expect objects that
// _can_ be cross-origin.
struct MaybeCrossOriginObjectThisPolicy;

// A this-extraction policy which is just like
// MaybeCrossOriginObjectThisPolicy but has lenient-this behavior.
struct MaybeCrossOriginObjectLenientThisPolicy;

// An exception-reporting policy for normal getters/setters/methods.
struct ThrowExceptions;

// An exception-handling policy for Promise-returning getters/methods.
struct ConvertExceptionsToPromises;
}  // namespace binding_detail

bool StaticMethodPromiseWrapper(JSContext* cx, unsigned argc, JS::Value* vp);

// ConvertExceptionToPromise should only be called when we have an error
// condition (e.g. returned false from a JSAPI method).  Note that there may be
// no exception on cx, in which case this is an uncatchable failure that will
// simply be propagated.  Otherwise this method will attempt to convert the
// exception to a Promise rejected with the exception that it will store in
// rval.
bool ConvertExceptionToPromise(JSContext* cx,
                               JS::MutableHandle<JS::Value> rval);

#ifdef DEBUG
void AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitinfo,
                                    JS::Handle<JS::Value> aValue);
#endif

bool CallerSubsumes(JSObject* aObject);

MOZ_ALWAYS_INLINE bool CallerSubsumes(JS::Handle<JS::Value> aValue) {
  if (!aValue.isObject()) {
    return true;
  }
  return CallerSubsumes(&aValue.toObject());
}

template <class T, class S>
inline RefPtr<T> StrongOrRawPtr(already_AddRefed<S>&& aPtr) {
  return std::move(aPtr);
}

template <class T, class S>
inline RefPtr<T> StrongOrRawPtr(RefPtr<S>&& aPtr) {
  return std::move(aPtr);
}

template <class T, class ReturnType = std::conditional_t<IsRefcounted<T>::value,
                                                         T*, UniquePtr<T>>>
inline ReturnType StrongOrRawPtr(T* aPtr) {
  return ReturnType(aPtr);
}

template <class T, template <typename> class SmartPtr, class S>
inline void StrongOrRawPtr(SmartPtr<S>&& aPtr) = delete;

template <class T>
using StrongPtrForMember =
    std::conditional_t<IsRefcounted<T>::value, RefPtr<T>, UniquePtr<T>>;

namespace binding_detail {
inline JSObject* GetHackedNamespaceProtoObject(JSContext* aCx) {
  return JS_NewPlainObject(aCx);
}
}  // namespace binding_detail

// Resolve an id on the given global object that wants to be included in
// Exposed=System webidl annotations.  False return value means exception
// thrown.
bool SystemGlobalResolve(JSContext* cx, JS::Handle<JSObject*> obj,
                         JS::Handle<jsid> id, bool* resolvedp);

// Enumerate all ids on the given global object that wants to be included in
// Exposed=System webidl annotations.  False return value means exception
// thrown.
bool SystemGlobalEnumerate(JSContext* cx, JS::Handle<JSObject*> obj);

// Slot indexes for maplike/setlike forEach functions
#define FOREACH_CALLBACK_SLOT 0
#define FOREACH_MAPLIKEORSETLIKEOBJ_SLOT 1

// Backing function for running .forEach() on maplike/setlike interfaces.
// Unpacks callback and maplike/setlike object from reserved slots, then runs
// callback for each key (and value, for maplikes)
bool ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp);

// Unpacks backing object (ES6 map/set) from the reserved slot of a reflector
// for a maplike/setlike interface. If backing object does not exist, creates
// backing object in the compartment of the reflector involved, making this safe
// to use across compartments/via xrays. Return values of these methods will
// always be in the context compartment.
bool GetMaplikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
                             size_t aSlotIndex,
                             JS::MutableHandle<JSObject*> aBackingObj,
                             bool* aBackingObjCreated);
bool GetSetlikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
                             size_t aSlotIndex,
                             JS::MutableHandle<JSObject*> aBackingObj,
                             bool* aBackingObjCreated);

// Unpacks backing object (ES Proxy exotic object) from the reserved slot of a
// reflector for a observableArray attribute. If backing object does not exist,
// creates backing object in the compartment of the reflector involved, making
// this safe to use across compartments/via xrays. Return values of these
// methods will always be in the context compartment.
bool GetObservableArrayBackingObject(
    JSContext* aCx, JS::Handle<JSObject*> aObj, size_t aSlotIndex,
    JS::MutableHandle<JSObject*> aBackingObj, bool* aBackingObjCreated,
    const ObservableArrayProxyHandler* aHandler, void* aOwner);

// Get the desired prototype object for an object construction from the given
// CallArgs.  The CallArgs must be for a constructor call.  The
// aProtoId/aCreator arguments are used to get a default if we don't find a
// prototype on the newTarget of the callargs.
bool GetDesiredProto(JSContext* aCx, const JS::CallArgs& aCallArgs,
                     prototypes::id::ID aProtoId,
                     CreateInterfaceObjectsMethod aCreator,
                     JS::MutableHandle<JSObject*> aDesiredProto);

// This function is expected to be called from the constructor function for an
// HTML or XUL element interface; the global/callargs need to be whatever was
// passed to that constructor function.
already_AddRefed<Element> CreateXULOrHTMLElement(
    const GlobalObject& aGlobal, const JS::CallArgs& aCallArgs,
    JS::Handle<JSObject*> aGivenProto, ErrorResult& aRv);

void SetUseCounter(JSObject* aObject, UseCounter aUseCounter);
void SetUseCounter(UseCounterWorker aUseCounter);

// Warnings
void DeprecationWarning(JSContext* aCx, JSObject* aObject,
                        DeprecatedOperations aOperation);

void DeprecationWarning(const GlobalObject& aGlobal,
                        DeprecatedOperations aOperation);

// A callback to perform funToString on an interface object
JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject,
                                  unsigned /* indent */);

namespace binding_detail {
// Get a JS global object that can be used for some temporary allocations.  The
// idea is that this should be used for situations when you need to operate in
// _some_ compartment but don't care which one.  A typical example is when you
// have non-JS input, non-JS output, but have to go through some sort of JS
// representation in the middle, so need a compartment to allocate things in.
//
// It's VERY important that any consumers of this function only do things that
// are guaranteed to be side-effect-free, even in the face of a script
// environment controlled by a hostile adversary.  This is because in the worker
// case the global is in fact the worker global, so it and its standard objects
// are controlled by the worker script.  This is why this function is in the
// binding_detail namespace.  Any use of this function MUST be very carefully
// reviewed by someone who is sufficiently devious and has a very good
// understanding of all the code that will run while we're using the return
// value, including the SpiderMonkey parts.
JSObject* UnprivilegedJunkScopeOrWorkerGlobal(const fallible_t&);

// Implementation of the [HTMLConstructor] extended attribute.
bool HTMLConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
                     constructors::id::ID aConstructorId,
                     prototypes::id::ID aProtoId,
                     CreateInterfaceObjectsMethod aCreator);

// A method to test whether an attribute with the given JSJitGetterOp getter is
// enabled in the given set of prefable proeprty specs.  For use for toJSON
// conversions.  aObj is the object that would be used as the "this" value.
bool IsGetterEnabled(JSContext* aCx, JS::Handle<JSObject*> aObj,
                     JSJitGetterOp aGetter,
                     const Prefable<const JSPropertySpec>* aAttributes);

// A class that can be used to examine the chars of a linear string.
class StringIdChars {
 public:
  // Require a non-const ref to an AutoRequireNoGC to prevent callers
  // from passing temporaries.
  StringIdChars(JS::AutoRequireNoGC& nogc, JSLinearString* str) {
    mIsLatin1 = JS::LinearStringHasLatin1Chars(str);
    if (mIsLatin1) {
      mLatin1Chars = JS::GetLatin1LinearStringChars(nogc, str);
    } else {
      mTwoByteChars = JS::GetTwoByteLinearStringChars(nogc, str);
    }
#ifdef DEBUG
    mLength = JS::GetLinearStringLength(str);
#endif  // DEBUG
  }

  MOZ_ALWAYS_INLINE char16_t operator[](size_t index) {
    MOZ_ASSERT(index < mLength);
    if (mIsLatin1) {
      return mLatin1Chars[index];
    }
    return mTwoByteChars[index];
  }

 private:
  bool mIsLatin1;
  union {
    const JS::Latin1Char* mLatin1Chars;
    const char16_t* mTwoByteChars;
  };
#ifdef DEBUG
  size_t mLength;
#endif  // DEBUG
};

already_AddRefed<Promise> CreateRejectedPromiseFromThrownException(
    JSContext* aCx, ErrorResult& aError);

}  // namespace binding_detail

}  // namespace dom
}  // namespace mozilla

#endif /* mozilla_dom_BindingUtils_h__ */
