From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- dom/bindings/BindingUtils.h | 3267 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3267 insertions(+) create mode 100644 dom/bindings/BindingUtils.h (limited to 'dom/bindings/BindingUtils.h') diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h new file mode 100644 index 0000000000..fa1002af1c --- /dev/null +++ b/dom/bindings/BindingUtils.h @@ -0,0 +1,3267 @@ +/* -*- 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 + +#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 +class Record; +class WindowProxyHolder; + +enum class DeprecatedOperations : uint16_t; + +nsresult UnwrapArgImpl(JSContext* cx, JS::Handle 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 +inline nsresult UnwrapArg(JSContext* cx, JS::Handle src, + Interface** ppArg) { + return UnwrapArgImpl(cx, src, NS_GET_TEMPLATE_IID(Interface), + reinterpret_cast(ppArg)); +} + +nsresult UnwrapWindowProxyArg(JSContext* cx, JS::Handle 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 +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(val.toPrivate()); +} + +template +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(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(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, 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( \ + obj, value, cx) + +// Test whether the given object is an instance of the given interface. +#define IS_INSTANCE_OF(Interface, obj) \ + mozilla::dom::IsInstanceOf( \ + 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). +// +// 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, 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 +MOZ_ALWAYS_INLINE nsresult UnwrapObjectInternal(V& obj, U& value, + prototypes::ID protoID, + uint32_t protoDepth, + const CxType& cx) { + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v, + "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(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) { + 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) { + // 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(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 aHandle) + : mHandle(aHandle) {} + + void operator=(JSObject* aObject) { + MOZ_ASSERT(aObject); + mHandle.set(aObject); + } + + operator JSObject*() const { return mHandle; } + + private: + JS::MutableHandle mHandle; +}; + +struct MutableValueHandleWrapper { + explicit MutableValueHandleWrapper(JS::MutableHandle 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 mHandle; +}; + +} // namespace binding_detail + +// UnwrapObject overloads that ensure we have a MutableHandle to keep it alive. +template +MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::MutableHandle obj, + U& value, const CxType& cx) { + binding_detail::MutableObjectHandleWrapper wrapper(obj); + return binding_detail::UnwrapObjectInternal( + wrapper, value, PrototypeID, PrototypeTraits::Depth, cx); +} + +template +MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::MutableHandle obj, + U& value, const CxType& cx) { + MOZ_ASSERT(obj.isObject()); + binding_detail::MutableValueHandleWrapper wrapper(obj); + return binding_detail::UnwrapObjectInternal( + wrapper, value, PrototypeID, PrototypeTraits::Depth, cx); +} + +// UnwrapObject overloads that ensure we have a strong ref to keep it alive. +template +MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, RefPtr& value, + const CxType& cx) { + return binding_detail::UnwrapObjectInternal( + obj, value, PrototypeID, PrototypeTraits::Depth, cx); +} + +template +MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, nsCOMPtr& value, + const CxType& cx) { + return binding_detail::UnwrapObjectInternal( + obj, value, PrototypeID, PrototypeTraits::Depth, cx); +} + +template +MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, OwningNonNull& value, + const CxType& cx) { + return binding_detail::UnwrapObjectInternal( + obj, value, PrototypeID, PrototypeTraits::Depth, cx); +} + +template +MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, NonNull& value, + const CxType& cx) { + return binding_detail::UnwrapObjectInternal( + obj, value, PrototypeID, PrototypeTraits::Depth, cx); +} + +// An UnwrapObject overload that just calls one of the JSObject* ones. +template +MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::Handle obj, U& value, + const CxType& cx) { + MOZ_ASSERT(obj.isObject()); + return UnwrapObject(&obj.toObject(), value, cx); +} + +template +MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::Handle obj, + NonNull& value, const CxType& cx) { + MOZ_ASSERT(obj.isObject()); + return UnwrapObject(&obj.toObject(), value, cx); +} + +template +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 +MOZ_ALWAYS_INLINE nsresult UnwrapObjectWithCrossOriginAsserts(V&& obj, + U& value) { + AssertStaticUnwrapOK(); + return UnwrapObject(obj, value, nullptr); +} +} // namespace binding_detail + +template +MOZ_ALWAYS_INLINE bool IsInstanceOf(JSObject* obj) { + AssertStaticUnwrapOK(); + void* ignored; + nsresult unwrapped = binding_detail::UnwrapObjectInternal( + obj, ignored, PrototypeID, PrototypeTraits::Depth, nullptr); + return NS_SUCCEEDED(unwrapped); +} + +template +MOZ_ALWAYS_INLINE nsresult UnwrapNonWrapperObject(JSObject* obj, U& value) { + MOZ_ASSERT(!js::IsWrapper(obj)); + return binding_detail::UnwrapObjectInternal( + obj, value, PrototypeID, PrototypeTraits::Depth, nullptr); +} + +MOZ_ALWAYS_INLINE bool IsConvertibleToDictionary(JS::Handle 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, 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& EntrySlotOrCreate(size_t i) { return (*this)[i]; } + + JS::Heap& 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& 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& 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, kPageSize> Page; + static const size_t kNPages = + kProtoAndIfaceCacheCount / kPageSize + + size_t(bool(kProtoAndIfaceCacheCount % kPageSize)); + Array 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& 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& 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(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 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 global, + JS::Handle protoProto, const JSClass* protoClass, + JS::Heap* protoCache, JS::Handle constructorProto, + const JSClass* constructorClass, unsigned ctorNargs, + bool isConstructorChromeOnly, + const LegacyFactoryFunction* namedConstructors, + JS::Heap* 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 obj, + const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties); + +/* + * Define the legacy unforgeable methods on an object. + */ +bool DefineLegacyUnforgeableMethods( + JSContext* cx, JS::Handle obj, + const Prefable* props); + +/* + * Define the legacy unforgeable attributes on an object. + */ +bool DefineLegacyUnforgeableAttributes( + JSContext* cx, JS::Handle obj, + const Prefable* props); + +#define HAS_MEMBER_TYPEDEFS \ + private: \ + typedef char yes[1]; \ + typedef char no[2] + +#ifdef _MSC_VER +# define HAS_MEMBER_CHECK(_name) \ + template \ + static yes& Check##_name(char(*)[(&V::_name == 0) + 1]) +#else +# define HAS_MEMBER_CHECK(_name) \ + template \ + static yes& Check##_name(char(*)[sizeof(&V::_name) + 1]) +#endif + +#define HAS_MEMBER(_memberName, _valueName) \ + private: \ + HAS_MEMBER_CHECK(_memberName); \ + template \ + static no& Check##_memberName(...); \ + \ + public: \ + static bool const _valueName = \ + sizeof(Check##_memberName(nullptr)) == sizeof(yes) + +template +struct NativeHasMember { + HAS_MEMBER_TYPEDEFS; + + HAS_MEMBER(GetParentObject, GetParentObject); + HAS_MEMBER(WrapObject, WrapObject); +}; + +template +struct IsSmartPtr { + HAS_MEMBER_TYPEDEFS; + + HAS_MEMBER(get, value); +}; + +template +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::value || IsRefcounted::value, + "Classes derived from nsISupports are refcounted!"); +}; + +#undef HAS_MEMBER +#undef HAS_MEMBER_CHECK +#undef HAS_MEMBER_TYPEDEFS + +#ifdef DEBUG +template ::value> +struct CheckWrapperCacheCast { + static bool Check() { + return reinterpret_cast( + static_cast(reinterpret_cast(1))) == 1; + } +}; +template +struct CheckWrapperCacheCast { + static bool Check() { return true; } +}; +#endif + +inline bool TryToOuterize(JS::MutableHandle 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 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 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 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 which must be non-null. +MOZ_ALWAYS_INLINE +bool MaybeWrapObject(JSContext* cx, JS::MutableHandle 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 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 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 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 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 +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::value || + std::is_base_of::value || + std::is_same_v; +}; + +#ifdef DEBUG +template ::value> +struct CheckWrapperCacheTracing { + static inline void Check(T* aObject) {} +}; + +template +struct CheckWrapperCacheTracing { + 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(&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(&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 aGivenProto); +#endif // DEBUG + +template +MOZ_ALWAYS_INLINE bool DoGetOrCreateDOMReflector( + JSContext* cx, T* value, JS::Handle givenProto, + JS::MutableHandle 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::value) { + CheckWrapperCacheTracing::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::value)); + MOZ_ASSERT(CheckWrapperCacheCast::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::value ? TryToOuterize(rval) : true; + } + + if (wrapBehavior == eDontWrapIntoContextCompartment) { + if (TypeNeedsOuterization::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 +MOZ_ALWAYS_INLINE bool GetOrCreateDOMReflector( + JSContext* cx, T* value, JS::MutableHandle rval, + JS::Handle givenProto = nullptr) { + using namespace binding_detail; + return DoGetOrCreateDOMReflector( + 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 +MOZ_ALWAYS_INLINE bool GetOrCreateDOMReflectorNoWrap( + JSContext* cx, T* value, JS::MutableHandle rval) { + using namespace binding_detail; + return DoGetOrCreateDOMReflector( + cx, value, nullptr, rval); +} + +// Helper for different overloadings of WrapNewBindingNonWrapperCachedObject() +inline bool FinishWrapping(JSContext* cx, JS::Handle obj, + JS::MutableHandle 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 outparam. +template +inline bool WrapNewBindingNonWrapperCachedObject( + JSContext* cx, JS::Handle scopeArg, T* value, + JS::MutableHandle rval, + JS::Handle givenProto = nullptr) { + static_assert(IsRefcounted::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 obj(cx); + { + // scope for the JSAutoRealm so that we restore the realm + // before we call JS_WrapValue. + Maybe ar; + // Maybe 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 scope(cx, scopeArg); + JS::Rooted 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 outparam and a boolean return value that is +// true if the JSObject took ownership +template +inline bool WrapNewBindingNonWrapperCachedObject( + JSContext* cx, JS::Handle scopeArg, UniquePtr& value, + JS::MutableHandle rval, + JS::Handle givenProto = nullptr) { + static_assert(!IsRefcounted::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 obj(cx); + { + // scope for the JSAutoRealm so that we restore the realm + // before we call JS_WrapValue. + Maybe ar; + // Maybe 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 scope(cx, scopeArg); + JS::Rooted 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