/* -*- 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) { return (*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; } return (*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