summaryrefslogtreecommitdiffstats
path: root/dom/bindings/BindingUtils.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/bindings/BindingUtils.h')
-rw-r--r--dom/bindings/BindingUtils.h3260
1 files changed, 3260 insertions, 0 deletions
diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h
new file mode 100644
index 0000000000..df3c509ab7
--- /dev/null
+++ b/dom/bindings/BindingUtils.h
@@ -0,0 +1,3260 @@
+/* -*- 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 = is_dom_dictionary<T>,
+ bool isTypedArray = is_dom_typed_array<T>,
+ bool isOwningUnion = is_dom_owning_union<T>>
+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);
+
+// 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.
+ */
+inline bool NonVoidByteStringToJsval(JSContext* cx, const nsACString& str,
+ JS::MutableHandle<JS::Value> rval) {
+ return xpc::NonVoidLatin1StringToJsval(cx, str, 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) {
+ return xpc::NonVoidUTF8StringToJsval(cx, str, rval);
+}
+
+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,
+ 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;
+ }
+
+ // Initializing this at this point for nsGlobalWindowInner makes no sense,
+ // because GetRTPCallerType doesn't return the correct result before
+ // the global is completely initialized with a document.
+ if constexpr (!std::is_base_of_v<nsGlobalWindowInner, T>) {
+ JS::SetRealmReduceTimerPrecisionCallerType(
+ js::GetNonCCWObjectRealm(aGlobal),
+ RTPCallerTypeToToken(aNative->GetRTPCallerType()));
+ }
+ }
+
+ 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, typename = std::enable_if_t<IsRefcounted<T>::value>>
+inline T* StrongOrRawPtr(T* aPtr) {
+ return aPtr;
+}
+
+template <class T, class S,
+ typename = std::enable_if_t<!IsRefcounted<S>::value>>
+inline UniquePtr<T> StrongOrRawPtr(UniquePtr<S>&& aPtr) {
+ return std::move(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__ */