gecko-dev/dom/bindings/DOMJSClass.h
2018-08-20 07:46:08 -07:00

529 lines
19 KiB
C++

/* -*- 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 "jsapi.h"
#include "jsfriendapi.h"
#include "js/Wrapper.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 Realm 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 realm, regardless of whether the other realm is a
* Secure Context or not.
*
* Checking the JSContext's Realm 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)
{
MOZ_ASSERT(!js::IsWrapper(aObj));
return JS::GetIsSecureContext(js::GetContextRealm(aCx)) ||
JS::GetIsSecureContext(js::GetNonCCWObjectRealm(aObj));
}
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;
static const uint32_t AudioWorkletGlobalScope = 1u << 7;
} // 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::GetNonCCWObjectGlobal(obj),
nonExposedGlobals)) {
return false;
}
if (!enabled) {
return false;
}
if (secureContext && !IsSecureContextOrObjectIsFromSecureContext(cx, obj)) {
return false;
}
if (enabledFunc && !enabledFunc(cx, JS::GetNonCCWObjectGlobal(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 {
MOZ_ASSERT(!js::IsWrapper(obj));
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;
};
enum PropertyType {
eStaticMethod,
eStaticAttribute,
eMethod,
eAttribute,
eUnforgeableMethod,
eUnforgeableAttribute,
eConstant,
ePropertyTypeCount
};
#define NUM_BITS_PROPERTY_INFO_TYPE 3
#define NUM_BITS_PROPERTY_INFO_PREF_INDEX 13
#define NUM_BITS_PROPERTY_INFO_SPEC_INDEX 16
struct PropertyInfo {
private:
// MSVC generates static initializers if we store a jsid here, even if
// PropertyInfo has a constexpr constructor. See bug 1460341 and bug 1464036.
uintptr_t mIdBits;
public:
// One of PropertyType, will be used for accessing the corresponding Duo in
// NativePropertiesN.duos[].
uint32_t type: NUM_BITS_PROPERTY_INFO_TYPE;
// The index to the corresponding Preable in Duo.mPrefables[].
uint32_t prefIndex: NUM_BITS_PROPERTY_INFO_PREF_INDEX;
// The index to the corresponding spec in Duo.mPrefables[prefIndex].specs[].
uint32_t specIndex: NUM_BITS_PROPERTY_INFO_SPEC_INDEX;
void SetId(jsid aId) {
static_assert(sizeof(jsid) == sizeof(mIdBits), "jsid should fit in mIdBits");
mIdBits = JSID_BITS(aId);
}
MOZ_ALWAYS_INLINE jsid Id() const {
return jsid::fromRawBits(mIdBits);
}
};
static_assert(ePropertyTypeCount <= 1ull << NUM_BITS_PROPERTY_INFO_TYPE,
"We have property type count that is > (1 << NUM_BITS_PROPERTY_INFO_TYPE)");
// Conceptually, NativeProperties has seven (Prefable<T>*, PropertyInfo*) duos
// (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 14 pointers, but in most instances most of the duos 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 duos (stored in duos[]), plus four bits for each of the 7 possible
// duos: 1 bit that states if that duo is present, and 3 that state that duo's
// offset (if present) in duos[].
//
// All duo accesses should be done via the getters, which contain assertions
// that check we don't overrun the end of the struct. (The duo 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.
//
// We store all the property ids a NativePropertiesN owns in a single array of
// PropertyInfo structs. Each struct contains an id and the information needed
// to find the corresponding Prefable for the enabled check, as well as the
// information needed to find the correct property descriptor in the
// Prefable. We also store an array of indices into the PropertyInfo array,
// sorted by bits of the corresponding jsid. Given a jsid, this allows us to
// binary search for the index of the corresponding PropertyInfo, if any.
//
// 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 {
// Duo structs are stored in the duos[] array, and each element in the array
// could require a different T. Therefore, we can't use the correct type for
// mPrefables. Instead we use void* and cast to the correct type in the
// getters.
struct Duo {
const /*Prefable<const T>*/ void* const mPrefables;
PropertyInfo* const mPropertyInfos;
};
constexpr const NativePropertiesN<7>* Upcast() const {
return reinterpret_cast<const NativePropertiesN<7>*>(this);
}
const PropertyInfo* PropertyInfos() const {
return duos[0].mPropertyInfos;
}
#define DO(SpecT, FieldName) \
public: \
/* The bitfields indicating the duo's presence and (if present) offset. */ \
const uint32_t mHas##FieldName##s:1; \
const uint32_t m##FieldName##sOffset:3; \
private: \
const Duo* FieldName##sDuo() const { \
MOZ_ASSERT(Has##FieldName##s()); \
return &duos[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##sDuo()->mPrefables); \
} \
PropertyInfo* FieldName##PropertyInfos() const { \
return FieldName##sDuo()->mPropertyInfos; \
}
DO(JSFunctionSpec, StaticMethod)
DO(JSPropertySpec, StaticAttribute)
DO(JSFunctionSpec, Method)
DO(JSPropertySpec, Attribute)
DO(JSFunctionSpec, UnforgeableMethod)
DO(JSPropertySpec, UnforgeableAttribute)
DO(ConstantSpec, Constant)
#undef DO
// The index to the iterator method in MethodPropertyInfos() array.
const int16_t iteratorAliasMethodIndex;
// The number of PropertyInfo structs that the duos manage. This is the total
// count across all duos.
const uint16_t propertyInfoCount;
// The sorted indices array from sorting property ids, which will be used when
// we binary search for a property.
uint16_t* sortedPropertyIndices;
const Duo duos[N];
};
// Ensure the struct has the expected size. The 8 is for the bitfields plus
// iteratorAliasMethodIndex and idsLength; the rest is for the idsSortedIndex,
// and duos[].
static_assert(sizeof(NativePropertiesN<1>) == 8 + 3*sizeof(void*), "1 size");
static_assert(sizeof(NativePropertiesN<2>) == 8 + 5*sizeof(void*), "2 size");
static_assert(sizeof(NativePropertiesN<3>) == 8 + 7*sizeof(void*), "3 size");
static_assert(sizeof(NativePropertiesN<4>) == 8 + 9*sizeof(void*), "4 size");
static_assert(sizeof(NativePropertiesN<5>) == 8 + 11*sizeof(void*), "5 size");
static_assert(sizeof(NativePropertiesN<6>) == 8 + 13*sizeof(void*), "6 size");
static_assert(sizeof(NativePropertiesN<7>) == 8 + 15*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 */