Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #ifndef mozilla_dom_DOMJSClass_h
8 : #define mozilla_dom_DOMJSClass_h
9 :
10 : #include "jsfriendapi.h"
11 : #include "js/Wrapper.h"
12 : #include "mozilla/Assertions.h"
13 : #include "mozilla/Attributes.h"
14 : #include "mozilla/Likely.h"
15 :
16 : #include "mozilla/dom/PrototypeList.h" // auto-generated
17 :
18 : #include "mozilla/dom/JSSlots.h"
19 :
20 : class nsCycleCollectionParticipant;
21 :
22 : // All DOM globals must have a slot at DOM_PROTOTYPE_SLOT.
23 : #define DOM_PROTOTYPE_SLOT JSCLASS_GLOBAL_SLOT_COUNT
24 :
25 : // Keep this count up to date with any extra global slots added above.
26 : #define DOM_GLOBAL_SLOTS 1
27 :
28 : // We use these flag bits for the new bindings.
29 : #define JSCLASS_DOM_GLOBAL JSCLASS_USERBIT1
30 : #define JSCLASS_IS_DOMIFACEANDPROTOJSCLASS JSCLASS_USERBIT2
31 :
32 : namespace mozilla {
33 : namespace dom {
34 :
35 : /**
36 : * Returns true if code running in the given JSContext is allowed to access
37 : * [SecureContext] API on the given JSObject.
38 : *
39 : * [SecureContext] API exposure is restricted to use by code in a Secure
40 : * Contexts:
41 : *
42 : * https://w3c.github.io/webappsec-secure-contexts/
43 : *
44 : * Since we want [SecureContext] exposure to depend on the privileges of the
45 : * running code (rather than the privileges of an object's creator), this
46 : * function checks to see whether the given JSContext's Realm is flagged
47 : * as a Secure Context. That allows us to make sure that system principal code
48 : * (which is marked as a Secure Context) can access Secure Context API on an
49 : * object in a different realm, regardless of whether the other realm is a
50 : * Secure Context or not.
51 : *
52 : * Checking the JSContext's Realm doesn't work for expanded principal
53 : * globals accessing a Secure Context web page though (e.g. those used by frame
54 : * scripts). To handle that we fall back to checking whether the JSObject came
55 : * from a Secure Context.
56 : *
57 : * Note: We'd prefer this function to live in BindingUtils.h, but we need to
58 : * call it in this header, and BindingUtils.h includes us (i.e. we'd have a
59 : * circular dependency between headers if it lived there).
60 : */
61 : inline bool
62 0 : IsSecureContextOrObjectIsFromSecureContext(JSContext* aCx, JSObject* aObj)
63 : {
64 0 : MOZ_ASSERT(!js::IsWrapper(aObj));
65 0 : return JS::GetIsSecureContext(js::GetContextRealm(aCx)) ||
66 42 : JS::GetIsSecureContext(js::GetNonCCWObjectRealm(aObj));
67 : }
68 :
69 : typedef bool
70 : (* ResolveOwnProperty)(JSContext* cx, JS::Handle<JSObject*> wrapper,
71 : JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
72 : JS::MutableHandle<JS::PropertyDescriptor> desc);
73 :
74 : typedef bool
75 : (* EnumerateOwnProperties)(JSContext* cx, JS::Handle<JSObject*> wrapper,
76 : JS::Handle<JSObject*> obj,
77 : JS::AutoIdVector& props);
78 :
79 : typedef bool
80 : (* DeleteNamedProperty)(JSContext* cx, JS::Handle<JSObject*> wrapper,
81 : JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
82 : JS::ObjectOpResult& opresult);
83 :
84 : // Returns true if the given global is of a type whose bit is set in
85 : // aNonExposedGlobals.
86 : bool
87 : IsNonExposedGlobal(JSContext* aCx, JSObject* aGlobal,
88 : uint32_t aNonExposedGlobals);
89 :
90 : struct ConstantSpec
91 : {
92 : const char* name;
93 : JS::Value value;
94 : };
95 :
96 : typedef bool (*PropertyEnabled)(JSContext* cx, JSObject* global);
97 :
98 : namespace GlobalNames {
99 : // The names of our possible globals. These are the names of the actual
100 : // interfaces, not of the global names used to refer to them in IDL [Exposed]
101 : // annotations.
102 : static const uint32_t Window = 1u << 0;
103 : static const uint32_t BackstagePass = 1u << 1;
104 : static const uint32_t DedicatedWorkerGlobalScope = 1u << 2;
105 : static const uint32_t SharedWorkerGlobalScope = 1u << 3;
106 : static const uint32_t ServiceWorkerGlobalScope = 1u << 4;
107 : static const uint32_t WorkerDebuggerGlobalScope = 1u << 5;
108 : static const uint32_t WorkletGlobalScope = 1u << 6;
109 : } // namespace GlobalNames
110 :
111 : struct PrefableDisablers {
112 954 : inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const {
113 : // Reading "enabled" on a worker thread is technically undefined behavior,
114 : // because it's written only on main threads, with no barriers of any sort.
115 : // So we want to avoid doing that. But we don't particularly want to make
116 : // expensive NS_IsMainThread calls here.
117 : //
118 : // The good news is that "enabled" is only written for things that have a
119 : // Pref annotation, and such things can never be exposed on non-Window
120 : // globals; our IDL parser enforces that. So as long as we check our
121 : // exposure set before checking "enabled" we will be ok.
122 0 : if (nonExposedGlobals &&
123 290 : IsNonExposedGlobal(cx, js::GetGlobalForObjectCrossCompartment(obj),
124 : nonExposedGlobals)) {
125 : return false;
126 : }
127 906 : if (!enabled) {
128 : return false;
129 : }
130 838 : if (secureContext && !IsSecureContextOrObjectIsFromSecureContext(cx, obj)) {
131 : return false;
132 : }
133 0 : if (enabledFunc &&
134 1042 : !enabledFunc(cx, js::GetGlobalForObjectCrossCompartment(obj))) {
135 : return false;
136 : }
137 648 : return true;
138 : }
139 :
140 : // A boolean indicating whether this set of specs is enabled. Not const
141 : // because it will change at runtime if the corresponding pref is changed.
142 : bool enabled;
143 :
144 : // A boolean indicating whether a Secure Context is required.
145 : const bool secureContext;
146 :
147 : // Bitmask of global names that we should not be exposed in.
148 : const uint16_t nonExposedGlobals;
149 :
150 : // A function pointer to a function that can say the property is disabled
151 : // even if "enabled" is set to true. If the pointer is null the value of
152 : // "enabled" is used as-is.
153 : const PropertyEnabled enabledFunc;
154 : };
155 :
156 : template<typename T>
157 : struct Prefable {
158 0 : inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const {
159 0 : MOZ_ASSERT(!js::IsWrapper(obj));
160 2729 : if (MOZ_LIKELY(!disablers)) {
161 : return true;
162 : }
163 954 : return disablers->isEnabled(cx, obj);
164 : }
165 :
166 : // Things that can disable this set of specs. |nullptr| means "cannot be
167 : // disabled".
168 : PrefableDisablers* const disablers;
169 :
170 : // Array of specs, terminated in whatever way is customary for T.
171 : // Null to indicate a end-of-array for Prefable, when such an
172 : // indicator is needed.
173 : const T* const specs;
174 : };
175 :
176 : enum PropertyType {
177 : eStaticMethod,
178 : eStaticAttribute,
179 : eMethod,
180 : eAttribute,
181 : eUnforgeableMethod,
182 : eUnforgeableAttribute,
183 : eConstant,
184 : ePropertyTypeCount
185 : };
186 :
187 : #define NUM_BITS_PROPERTY_INFO_TYPE 3
188 : #define NUM_BITS_PROPERTY_INFO_PREF_INDEX 13
189 : #define NUM_BITS_PROPERTY_INFO_SPEC_INDEX 16
190 :
191 : struct PropertyInfo {
192 : private:
193 : // MSVC generates static initializers if we store a jsid here, even if
194 : // PropertyInfo has a constexpr constructor. See bug 1460341 and bug 1464036.
195 : uintptr_t mIdBits;
196 : public:
197 : // One of PropertyType, will be used for accessing the corresponding Duo in
198 : // NativePropertiesN.duos[].
199 : uint32_t type: NUM_BITS_PROPERTY_INFO_TYPE;
200 : // The index to the corresponding Preable in Duo.mPrefables[].
201 : uint32_t prefIndex: NUM_BITS_PROPERTY_INFO_PREF_INDEX;
202 : // The index to the corresponding spec in Duo.mPrefables[prefIndex].specs[].
203 : uint32_t specIndex: NUM_BITS_PROPERTY_INFO_SPEC_INDEX;
204 :
205 : void SetId(jsid aId) {
206 : static_assert(sizeof(jsid) == sizeof(mIdBits), "jsid should fit in mIdBits");
207 2833 : mIdBits = JSID_BITS(aId);
208 : }
209 : MOZ_ALWAYS_INLINE jsid Id() const {
210 153884 : return jsid::fromRawBits(mIdBits);
211 : }
212 : };
213 :
214 : static_assert(ePropertyTypeCount <= 1ull << NUM_BITS_PROPERTY_INFO_TYPE,
215 : "We have property type count that is > (1 << NUM_BITS_PROPERTY_INFO_TYPE)");
216 :
217 : // Conceptually, NativeProperties has seven (Prefable<T>*, PropertyInfo*) duos
218 : // (where T is one of JSFunctionSpec, JSPropertySpec, or ConstantSpec), one for
219 : // each of: static methods and attributes, methods and attributes, unforgeable
220 : // methods and attributes, and constants.
221 : //
222 : // That's 14 pointers, but in most instances most of the duos are all null, and
223 : // there are many instances. To save space we use a variable-length type,
224 : // NativePropertiesN<N>, to hold the data and getters to access it. It has N
225 : // actual duos (stored in duos[]), plus four bits for each of the 7 possible
226 : // duos: 1 bit that states if that duo is present, and 3 that state that duo's
227 : // offset (if present) in duos[].
228 : //
229 : // All duo accesses should be done via the getters, which contain assertions
230 : // that check we don't overrun the end of the struct. (The duo data members are
231 : // public only so they can be statically initialized.) These assertions should
232 : // never fail so long as (a) accesses to the variable-length part are guarded by
233 : // appropriate Has*() calls, and (b) all instances are well-formed, i.e. the
234 : // value of N matches the number of mHas* members that are true.
235 : //
236 : // We store all the property ids a NativePropertiesN owns in a single array of
237 : // PropertyInfo structs. Each struct contains an id and the information needed
238 : // to find the corresponding Prefable for the enabled check, as well as the
239 : // information needed to find the correct property descriptor in the
240 : // Prefable. We also store an array of indices into the PropertyInfo array,
241 : // sorted by bits of the corresponding jsid. Given a jsid, this allows us to
242 : // binary search for the index of the corresponding PropertyInfo, if any.
243 : //
244 : // Finally, we define a typedef of NativePropertiesN<7>, NativeProperties, which
245 : // we use as a "base" type used to refer to all instances of NativePropertiesN.
246 : // (7 is used because that's the maximum valid parameter, though any other
247 : // value 1..6 could also be used.) This is reasonable because of the
248 : // aforementioned assertions in the getters. Upcast() is used to convert
249 : // specific instances to this "base" type.
250 : //
251 : template <int N>
252 : struct NativePropertiesN {
253 : // Duo structs are stored in the duos[] array, and each element in the array
254 : // could require a different T. Therefore, we can't use the correct type for
255 : // mPrefables. Instead we use void* and cast to the correct type in the
256 : // getters.
257 : struct Duo {
258 : const /*Prefable<const T>*/ void* const mPrefables;
259 : PropertyInfo* const mPropertyInfos;
260 : };
261 :
262 : constexpr const NativePropertiesN<7>* Upcast() const {
263 : return reinterpret_cast<const NativePropertiesN<7>*>(this);
264 : }
265 :
266 : const PropertyInfo* PropertyInfos() const {
267 : return duos[0].mPropertyInfos;
268 : }
269 :
270 : #define DO(SpecT, FieldName) \
271 : public: \
272 : /* The bitfields indicating the duo's presence and (if present) offset. */ \
273 : const uint32_t mHas##FieldName##s:1; \
274 : const uint32_t m##FieldName##sOffset:3; \
275 : private: \
276 : const Duo* FieldName##sDuo() const { \
277 : MOZ_ASSERT(Has##FieldName##s()); \
278 : return &duos[m##FieldName##sOffset]; \
279 : } \
280 : public: \
281 : bool Has##FieldName##s() const { \
282 : return mHas##FieldName##s; \
283 : } \
284 : const Prefable<const SpecT>* FieldName##s() const { \
285 : return static_cast<const Prefable<const SpecT>*> \
286 : (FieldName##sDuo()->mPrefables); \
287 : } \
288 : PropertyInfo* FieldName##PropertyInfos() const { \
289 : return FieldName##sDuo()->mPropertyInfos; \
290 : }
291 :
292 0 : DO(JSFunctionSpec, StaticMethod)
293 0 : DO(JSPropertySpec, StaticAttribute)
294 0 : DO(JSFunctionSpec, Method)
295 0 : DO(JSPropertySpec, Attribute)
296 0 : DO(JSFunctionSpec, UnforgeableMethod)
297 0 : DO(JSPropertySpec, UnforgeableAttribute)
298 1692 : DO(ConstantSpec, Constant)
299 :
300 : #undef DO
301 :
302 : // The index to the iterator method in MethodPropertyInfos() array.
303 : const int16_t iteratorAliasMethodIndex;
304 : // The number of PropertyInfo structs that the duos manage. This is the total
305 : // count across all duos.
306 : const uint16_t propertyInfoCount;
307 : // The sorted indices array from sorting property ids, which will be used when
308 : // we binary search for a property.
309 : uint16_t* sortedPropertyIndices;
310 :
311 : const Duo duos[N];
312 : };
313 :
314 : // Ensure the struct has the expected size. The 8 is for the bitfields plus
315 : // iteratorAliasMethodIndex and idsLength; the rest is for the idsSortedIndex,
316 : // and duos[].
317 : static_assert(sizeof(NativePropertiesN<1>) == 8 + 3*sizeof(void*), "1 size");
318 : static_assert(sizeof(NativePropertiesN<2>) == 8 + 5*sizeof(void*), "2 size");
319 : static_assert(sizeof(NativePropertiesN<3>) == 8 + 7*sizeof(void*), "3 size");
320 : static_assert(sizeof(NativePropertiesN<4>) == 8 + 9*sizeof(void*), "4 size");
321 : static_assert(sizeof(NativePropertiesN<5>) == 8 + 11*sizeof(void*), "5 size");
322 : static_assert(sizeof(NativePropertiesN<6>) == 8 + 13*sizeof(void*), "6 size");
323 : static_assert(sizeof(NativePropertiesN<7>) == 8 + 15*sizeof(void*), "7 size");
324 :
325 : // The "base" type.
326 : typedef NativePropertiesN<7> NativeProperties;
327 :
328 : struct NativePropertiesHolder
329 : {
330 : const NativeProperties* regular;
331 : const NativeProperties* chromeOnly;
332 : };
333 :
334 : // Helper structure for Xrays for DOM binding objects. The same instance is used
335 : // for instances, interface objects and interface prototype objects of a
336 : // specific interface.
337 : struct NativePropertyHooks
338 : {
339 : // The hook to call for resolving indexed or named properties. May be null if
340 : // there can't be any.
341 : ResolveOwnProperty mResolveOwnProperty;
342 : // The hook to call for enumerating indexed or named properties. May be null
343 : // if there can't be any.
344 : EnumerateOwnProperties mEnumerateOwnProperties;
345 : // The hook to call to delete a named property. May be null if there are no
346 : // named properties or no named property deleter. On success (true return)
347 : // the "found" argument will be set to true if there was in fact such a named
348 : // property and false otherwise. If it's set to false, the caller is expected
349 : // to proceed with whatever deletion behavior it would have if there were no
350 : // named properties involved at all (i.e. if the hook were null). If it's set
351 : // to true, it will indicate via opresult whether the delete actually
352 : // succeeded.
353 : DeleteNamedProperty mDeleteNamedProperty;
354 :
355 : // The property arrays for this interface.
356 : NativePropertiesHolder mNativeProperties;
357 :
358 : // This will be set to the ID of the interface prototype object for the
359 : // interface, if it has one. If it doesn't have one it will be set to
360 : // prototypes::id::_ID_Count.
361 : prototypes::ID mPrototypeID;
362 :
363 : // This will be set to the ID of the interface object for the interface, if it
364 : // has one. If it doesn't have one it will be set to
365 : // constructors::id::_ID_Count.
366 : constructors::ID mConstructorID;
367 :
368 : // The NativePropertyHooks instance for the parent interface (for
369 : // ShimInterfaceInfo).
370 : const NativePropertyHooks* mProtoHooks;
371 :
372 : // The JSClass to use for expandos on our Xrays. Can be null, in which case
373 : // Xrays will use a default class of their choice.
374 : const JSClass* mXrayExpandoClass;
375 : };
376 :
377 : enum DOMObjectType : uint8_t {
378 : eInstance,
379 : eGlobalInstance,
380 : eInterface,
381 : eInterfacePrototype,
382 : eGlobalInterfacePrototype,
383 : eNamedPropertiesObject
384 : };
385 :
386 : inline
387 : bool
388 : IsInstance(DOMObjectType type)
389 : {
390 : return type == eInstance || type == eGlobalInstance;
391 : }
392 :
393 : inline
394 : bool
395 : IsInterfacePrototype(DOMObjectType type)
396 : {
397 148 : return type == eInterfacePrototype || type == eGlobalInterfacePrototype;
398 : }
399 :
400 : typedef JSObject* (*AssociatedGlobalGetter)(JSContext* aCx,
401 : JS::Handle<JSObject*> aObj);
402 :
403 : typedef JSObject* (*ProtoGetter)(JSContext* aCx);
404 :
405 : /**
406 : * Returns a handle to the relevant WebIDL prototype object for the current
407 : * compartment global (which may be a handle to null on out of memory). Once
408 : * allocated, the prototype object is guaranteed to exist as long as the global
409 : * does, since the global traces its array of WebIDL prototypes and
410 : * constructors.
411 : */
412 : typedef JS::Handle<JSObject*> (*ProtoHandleGetter)(JSContext* aCx);
413 :
414 : // Special JSClass for reflected DOM objects.
415 : struct DOMJSClass
416 : {
417 : // It would be nice to just inherit from JSClass, but that precludes pure
418 : // compile-time initialization of the form |DOMJSClass = {...};|, since C++
419 : // only allows brace initialization for aggregate/POD types.
420 : const js::Class mBase;
421 :
422 : // A list of interfaces that this object implements, in order of decreasing
423 : // derivedness.
424 : const prototypes::ID mInterfaceChain[MAX_PROTOTYPE_CHAIN_LENGTH];
425 :
426 : // We store the DOM object in reserved slot with index DOM_OBJECT_SLOT or in
427 : // the proxy private if we use a proxy object.
428 : // Sometimes it's an nsISupports and sometimes it's not; this class tells
429 : // us which it is.
430 : const bool mDOMObjectIsISupports;
431 :
432 : const NativePropertyHooks* mNativeHooks;
433 :
434 : // A callback to find the associated global for our C++ object. Note that
435 : // this is used in cases when that global is _changing_, so it will not match
436 : // the global of the JSObject* passed in to this function!
437 : AssociatedGlobalGetter mGetAssociatedGlobal;
438 : ProtoHandleGetter mGetProto;
439 :
440 : // This stores the CC participant for the native, null if this class does not
441 : // implement cycle collection or if it inherits from nsISupports (we can get
442 : // the CC participant by QI'ing in that case).
443 : nsCycleCollectionParticipant* mParticipant;
444 :
445 0 : static const DOMJSClass* FromJSClass(const JSClass* base) {
446 0 : MOZ_ASSERT(base->flags & JSCLASS_IS_DOMJSCLASS);
447 53429 : return reinterpret_cast<const DOMJSClass*>(base);
448 : }
449 :
450 : static const DOMJSClass* FromJSClass(const js::Class* base) {
451 53429 : return FromJSClass(Jsvalify(base));
452 : }
453 :
454 6380 : const JSClass* ToJSClass() const { return Jsvalify(&mBase); }
455 : };
456 :
457 : // Special JSClass for DOM interface and interface prototype objects.
458 : struct DOMIfaceAndProtoJSClass
459 : {
460 : // It would be nice to just inherit from js::Class, but that precludes pure
461 : // compile-time initialization of the form
462 : // |DOMJSInterfaceAndPrototypeClass = {...};|, since C++ only allows brace
463 : // initialization for aggregate/POD types.
464 : const js::Class mBase;
465 :
466 : // Either eInterface, eInterfacePrototype, eGlobalInterfacePrototype or
467 : // eNamedPropertiesObject.
468 : DOMObjectType mType; // uint8_t
469 :
470 : // Boolean indicating whether this object wants a @@hasInstance property
471 : // pointing to InterfaceHasInstance defined on it. Only ever true for the
472 : // eInterface case.
473 : bool wantsInterfaceHasInstance;
474 :
475 : const prototypes::ID mPrototypeID; // uint16_t
476 : const uint32_t mDepth;
477 :
478 : const NativePropertyHooks* mNativeHooks;
479 :
480 : // The value to return for toString() on this interface or interface prototype
481 : // object.
482 : const char* mToString;
483 :
484 : ProtoGetter mGetParentProto;
485 :
486 0 : static const DOMIfaceAndProtoJSClass* FromJSClass(const JSClass* base) {
487 0 : MOZ_ASSERT(base->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS);
488 1301 : return reinterpret_cast<const DOMIfaceAndProtoJSClass*>(base);
489 : }
490 : static const DOMIfaceAndProtoJSClass* FromJSClass(const js::Class* base) {
491 1301 : return FromJSClass(Jsvalify(base));
492 : }
493 :
494 : const JSClass* ToJSClass() const { return Jsvalify(&mBase); }
495 : };
496 :
497 : class ProtoAndIfaceCache;
498 :
499 : inline bool
500 0 : DOMGlobalHasProtoAndIFaceCache(JSObject* global)
501 : {
502 0 : MOZ_ASSERT(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL);
503 : // This can be undefined if we GC while creating the global
504 0 : return !js::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).isUndefined();
505 : }
506 :
507 : inline bool
508 0 : HasProtoAndIfaceCache(JSObject* global)
509 : {
510 0 : if (!(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) {
511 : return false;
512 : }
513 0 : return DOMGlobalHasProtoAndIFaceCache(global);
514 : }
515 :
516 : inline ProtoAndIfaceCache*
517 9714 : GetProtoAndIfaceCache(JSObject* global)
518 : {
519 9714 : MOZ_ASSERT(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL);
520 : return static_cast<ProtoAndIfaceCache*>(
521 : js::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).toPrivate());
522 : }
523 :
524 : } // namespace dom
525 : } // namespace mozilla
526 :
527 : #endif /* mozilla_dom_DOMJSClass_h */
|