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