/* -*- 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_DOMJSClass_h #define mozilla_dom_DOMJSClass_h #include "jsfriendapi.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/Likely.h" #include "mozilla/dom/PrototypeList.h" // auto-generated #include "mozilla/dom/JSSlots.h" class nsCycleCollectionParticipant; // All DOM globals must have a slot at DOM_PROTOTYPE_SLOT. #define DOM_PROTOTYPE_SLOT JSCLASS_GLOBAL_SLOT_COUNT // Keep this count up to date with any extra global slots added above. #define DOM_GLOBAL_SLOTS 1 // We use these flag bits for the new bindings. #define JSCLASS_DOM_GLOBAL JSCLASS_USERBIT1 #define JSCLASS_IS_DOMIFACEANDPROTOJSCLASS JSCLASS_USERBIT2 namespace mozilla { namespace dom { /** * Returns true if code running in the given JSContext is allowed to access * [SecureContext] API on the given JSObject. * * [SecureContext] API exposure is restricted to use by code in a Secure * Contexts: * * https://w3c.github.io/webappsec-secure-contexts/ * * Since we want [SecureContext] exposure to depend on the privileges of the * running code (rather than the privileges of an object's creator), this * function checks to see whether the given JSContext's Compartment is flagged * as a Secure Context. That allows us to make sure that system principal code * (which is marked as a Secure Context) can access Secure Context API on an * object in a different compartment, regardless of whether the other * compartment is a Secure Context or not. * * Checking the JSContext's Compartment doesn't work for expanded principal * globals accessing a Secure Context web page though (e.g. those used by frame * scripts). To handle that we fall back to checking whether the JSObject came * from a Secure Context. * * Note: We'd prefer this function to live in BindingUtils.h, but we need to * call it in this header, and BindingUtils.h includes us (i.e. we'd have a * circular dependency between headers if it lived there). */ inline bool IsSecureContextOrObjectIsFromSecureContext(JSContext* aCx, JSObject* aObj) { return JS::CompartmentCreationOptionsRef(js::GetContextCompartment(aCx)).secureContext() || JS::CompartmentCreationOptionsRef(js::GetObjectCompartment(aObj)).secureContext(); } typedef bool (* ResolveOwnProperty)(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::MutableHandle<JS::PropertyDescriptor> desc); typedef bool (* EnumerateOwnProperties)(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj, JS::AutoIdVector& props); typedef bool (* DeleteNamedProperty)(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::ObjectOpResult& opresult); // Returns true if the given global is of a type whose bit is set in // aNonExposedGlobals. bool IsNonExposedGlobal(JSContext* aCx, JSObject* aGlobal, uint32_t aNonExposedGlobals); struct ConstantSpec { const char* name; JS::Value value; }; typedef bool (*PropertyEnabled)(JSContext* cx, JSObject* global); namespace GlobalNames { // The names of our possible globals. These are the names of the actual // interfaces, not of the global names used to refer to them in IDL [Exposed] // annotations. static const uint32_t Window = 1u << 0; static const uint32_t BackstagePass = 1u << 1; static const uint32_t DedicatedWorkerGlobalScope = 1u << 2; static const uint32_t SharedWorkerGlobalScope = 1u << 3; static const uint32_t ServiceWorkerGlobalScope = 1u << 4; static const uint32_t WorkerDebuggerGlobalScope = 1u << 5; static const uint32_t WorkletGlobalScope = 1u << 6; } // namespace GlobalNames struct PrefableDisablers { inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const { // Reading "enabled" on a worker thread is technically undefined behavior, // because it's written only on main threads, with no barriers of any sort. // So we want to avoid doing that. But we don't particularly want to make // expensive NS_IsMainThread calls here. // // The good news is that "enabled" is only written for things that have a // Pref annotation, and such things can never be exposed on non-Window // globals; our IDL parser enforces that. So as long as we check our // exposure set before checking "enabled" we will be ok. if (nonExposedGlobals && IsNonExposedGlobal(cx, js::GetGlobalForObjectCrossCompartment(obj), nonExposedGlobals)) { return false; } if (!enabled) { return false; } if (secureContext && !IsSecureContextOrObjectIsFromSecureContext(cx, obj)) { return false; } if (enabledFunc && !enabledFunc(cx, js::GetGlobalForObjectCrossCompartment(obj))) { return false; } return true; } // A boolean indicating whether this set of specs is enabled. Not const // because it will change at runtime if the corresponding pref is changed. bool enabled; // A boolean indicating whether a Secure Context is required. const bool secureContext; // Bitmask of global names that we should not be exposed in. const uint16_t nonExposedGlobals; // A function pointer to a function that can say the property is disabled // even if "enabled" is set to true. If the pointer is null the value of // "enabled" is used as-is. const PropertyEnabled enabledFunc; }; template<typename T> struct Prefable { inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const { if (MOZ_LIKELY(!disablers)) { return true; } return disablers->isEnabled(cx, obj); } // Things that can disable this set of specs. |nullptr| means "cannot be // disabled". PrefableDisablers* const disablers; // Array of specs, terminated in whatever way is customary for T. // Null to indicate a end-of-array for Prefable, when such an // indicator is needed. const T* const specs; }; // Conceptually, NativeProperties has seven (Prefable<T>*, jsid*, T*) trios // (where T is one of JSFunctionSpec, JSPropertySpec, or ConstantSpec), one for // each of: static methods and attributes, methods and attributes, unforgeable // methods and attributes, and constants. // // That's 21 pointers, but in most instances most of the trios are all null, // and there are many instances. To save space we use a variable-length type, // NativePropertiesN<N>, to hold the data and getters to access it. It has N // actual trios (stored in trios[]), plus four bits for each of the 7 possible // trios: 1 bit that states if that trio is present, and 3 that state that // trio's offset (if present) in trios[]. // // All trio accesses should be done via the getters, which contain assertions // that check we don't overrun the end of the struct. (The trio data members are // public only so they can be statically initialized.) These assertions should // never fail so long as (a) accesses to the variable-length part are guarded by // appropriate Has*() calls, and (b) all instances are well-formed, i.e. the // value of N matches the number of mHas* members that are true. // // Finally, we define a typedef of NativePropertiesN<7>, NativeProperties, which // we use as a "base" type used to refer to all instances of NativePropertiesN. // (7 is used because that's the maximum valid parameter, though any other // value 1..6 could also be used.) This is reasonable because of the // aforementioned assertions in the getters. Upcast() is used to convert // specific instances to this "base" type. // template <int N> struct NativePropertiesN { // Trio structs are stored in the trios[] array, and each element in the // array could require a different T. Therefore, we can't use the correct // type for mPrefables and mSpecs. Instead we use void* and cast to the // correct type in the getters. struct Trio { const /*Prefable<const T>*/ void* const mPrefables; const jsid* const mIds; const /*T*/ void* const mSpecs; }; const int32_t iteratorAliasMethodIndex; constexpr const NativePropertiesN<7>* Upcast() const { return reinterpret_cast<const NativePropertiesN<7>*>(this); } #define DO(SpecT, FieldName) \ public: \ /* The bitfields indicating the trio's presence and (if present) offset. */ \ const uint32_t mHas##FieldName##s:1; \ const uint32_t m##FieldName##sOffset:3; \ private: \ const Trio* FieldName##sTrio() const { \ MOZ_ASSERT(Has##FieldName##s()); \ return &trios[m##FieldName##sOffset]; \ } \ public: \ bool Has##FieldName##s() const { \ return mHas##FieldName##s; \ } \ const Prefable<const SpecT>* FieldName##s() const { \ return static_cast<const Prefable<const SpecT>*> \ (FieldName##sTrio()->mPrefables); \ } \ const jsid* FieldName##Ids() const { \ return FieldName##sTrio()->mIds; \ } \ const SpecT* FieldName##Specs() const { \ return static_cast<const SpecT*>(FieldName##sTrio()->mSpecs); \ } DO(JSFunctionSpec, StaticMethod) DO(JSPropertySpec, StaticAttribute) DO(JSFunctionSpec, Method) DO(JSPropertySpec, Attribute) DO(JSFunctionSpec, UnforgeableMethod) DO(JSPropertySpec, UnforgeableAttribute) DO(ConstantSpec, Constant) #undef DO const Trio trios[N]; }; // Ensure the struct has the expected size. The 8 is for the // iteratorAliasMethodIndex plus the bitfields; the rest is for trios[]. static_assert(sizeof(NativePropertiesN<1>) == 8 + 3*sizeof(void*), "1 size"); static_assert(sizeof(NativePropertiesN<2>) == 8 + 6*sizeof(void*), "2 size"); static_assert(sizeof(NativePropertiesN<3>) == 8 + 9*sizeof(void*), "3 size"); static_assert(sizeof(NativePropertiesN<4>) == 8 + 12*sizeof(void*), "4 size"); static_assert(sizeof(NativePropertiesN<5>) == 8 + 15*sizeof(void*), "5 size"); static_assert(sizeof(NativePropertiesN<6>) == 8 + 18*sizeof(void*), "6 size"); static_assert(sizeof(NativePropertiesN<7>) == 8 + 21*sizeof(void*), "7 size"); // The "base" type. typedef NativePropertiesN<7> NativeProperties; struct NativePropertiesHolder { const NativeProperties* regular; const NativeProperties* chromeOnly; }; // Helper structure for Xrays for DOM binding objects. The same instance is used // for instances, interface objects and interface prototype objects of a // specific interface. struct NativePropertyHooks { // The hook to call for resolving indexed or named properties. May be null if // there can't be any. ResolveOwnProperty mResolveOwnProperty; // The hook to call for enumerating indexed or named properties. May be null // if there can't be any. EnumerateOwnProperties mEnumerateOwnProperties; // The hook to call to delete a named property. May be null if there are no // named properties or no named property deleter. On success (true return) // the "found" argument will be set to true if there was in fact such a named // property and false otherwise. If it's set to false, the caller is expected // to proceed with whatever deletion behavior it would have if there were no // named properties involved at all (i.e. if the hook were null). If it's set // to true, it will indicate via opresult whether the delete actually // succeeded. DeleteNamedProperty mDeleteNamedProperty; // The property arrays for this interface. NativePropertiesHolder mNativeProperties; // This will be set to the ID of the interface prototype object for the // interface, if it has one. If it doesn't have one it will be set to // prototypes::id::_ID_Count. prototypes::ID mPrototypeID; // This will be set to the ID of the interface object for the interface, if it // has one. If it doesn't have one it will be set to // constructors::id::_ID_Count. constructors::ID mConstructorID; // The NativePropertyHooks instance for the parent interface (for // ShimInterfaceInfo). const NativePropertyHooks* mProtoHooks; // The JSClass to use for expandos on our Xrays. Can be null, in which case // Xrays will use a default class of their choice. const JSClass* mXrayExpandoClass; }; enum DOMObjectType : uint8_t { eInstance, eGlobalInstance, eInterface, eInterfacePrototype, eGlobalInterfacePrototype, eNamedPropertiesObject }; inline bool IsInstance(DOMObjectType type) { return type == eInstance || type == eGlobalInstance; } inline bool IsInterfacePrototype(DOMObjectType type) { return type == eInterfacePrototype || type == eGlobalInterfacePrototype; } typedef JSObject* (*AssociatedGlobalGetter)(JSContext* aCx, JS::Handle<JSObject*> aObj); typedef JSObject* (*ProtoGetter)(JSContext* aCx); /** * Returns a handle to the relevant WebIDL prototype object for the current * compartment global (which may be a handle to null on out of memory). Once * allocated, the prototype object is guaranteed to exist as long as the global * does, since the global traces its array of WebIDL prototypes and * constructors. */ typedef JS::Handle<JSObject*> (*ProtoHandleGetter)(JSContext* aCx); // Special JSClass for reflected DOM objects. struct DOMJSClass { // It would be nice to just inherit from JSClass, but that precludes pure // compile-time initialization of the form |DOMJSClass = {...};|, since C++ // only allows brace initialization for aggregate/POD types. const js::Class mBase; // A list of interfaces that this object implements, in order of decreasing // derivedness. const prototypes::ID mInterfaceChain[MAX_PROTOTYPE_CHAIN_LENGTH]; // We store the DOM object in reserved slot with index DOM_OBJECT_SLOT or in // the proxy private if we use a proxy object. // Sometimes it's an nsISupports and sometimes it's not; this class tells // us which it is. const bool mDOMObjectIsISupports; const NativePropertyHooks* mNativeHooks; // A callback to find the associated global for our C++ object. Note that // this is used in cases when that global is _changing_, so it will not match // the global of the JSObject* passed in to this function! AssociatedGlobalGetter mGetAssociatedGlobal; ProtoHandleGetter mGetProto; // This stores the CC participant for the native, null if this class does not // implement cycle collection or if it inherits from nsISupports (we can get // the CC participant by QI'ing in that case). nsCycleCollectionParticipant* mParticipant; static const DOMJSClass* FromJSClass(const JSClass* base) { MOZ_ASSERT(base->flags & JSCLASS_IS_DOMJSCLASS); return reinterpret_cast<const DOMJSClass*>(base); } static const DOMJSClass* FromJSClass(const js::Class* base) { return FromJSClass(Jsvalify(base)); } const JSClass* ToJSClass() const { return Jsvalify(&mBase); } }; // Special JSClass for DOM interface and interface prototype objects. struct DOMIfaceAndProtoJSClass { // It would be nice to just inherit from js::Class, but that precludes pure // compile-time initialization of the form // |DOMJSInterfaceAndPrototypeClass = {...};|, since C++ only allows brace // initialization for aggregate/POD types. const js::Class mBase; // Either eInterface, eInterfacePrototype, eGlobalInterfacePrototype or // eNamedPropertiesObject. DOMObjectType mType; // uint8_t // Boolean indicating whether this object wants a @@hasInstance property // pointing to InterfaceHasInstance defined on it. Only ever true for the // eInterface case. bool wantsInterfaceHasInstance; const prototypes::ID mPrototypeID; // uint16_t const uint32_t mDepth; const NativePropertyHooks* mNativeHooks; // The value to return for toString() on this interface or interface prototype // object. const char* mToString; ProtoGetter mGetParentProto; static const DOMIfaceAndProtoJSClass* FromJSClass(const JSClass* base) { MOZ_ASSERT(base->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS); return reinterpret_cast<const DOMIfaceAndProtoJSClass*>(base); } static const DOMIfaceAndProtoJSClass* FromJSClass(const js::Class* base) { return FromJSClass(Jsvalify(base)); } const JSClass* ToJSClass() const { return Jsvalify(&mBase); } }; class ProtoAndIfaceCache; inline bool DOMGlobalHasProtoAndIFaceCache(JSObject* global) { MOZ_ASSERT(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL); // This can be undefined if we GC while creating the global return !js::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).isUndefined(); } inline bool HasProtoAndIfaceCache(JSObject* global) { if (!(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) { return false; } return DOMGlobalHasProtoAndIFaceCache(global); } inline ProtoAndIfaceCache* GetProtoAndIfaceCache(JSObject* global) { MOZ_ASSERT(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL); return static_cast<ProtoAndIfaceCache*>( js::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).toPrivate()); } } // namespace dom } // namespace mozilla #endif /* mozilla_dom_DOMJSClass_h */