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
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #ifndef nsWrapperCache_h___
8 : #define nsWrapperCache_h___
9 :
10 : #include "nsCycleCollectionParticipant.h"
11 : #include "mozilla/Assertions.h"
12 : #include "js/Id.h" // must come before js/RootingAPI.h
13 : #include "js/Value.h" // must come before js/RootingAPI.h
14 : #include "js/RootingAPI.h"
15 : #include "js/TracingAPI.h"
16 :
17 : namespace mozilla {
18 : namespace dom {
19 : class TabChildGlobal;
20 : class ProcessGlobal;
21 : } // namespace dom
22 : } // namespace mozilla
23 : class SandboxPrivate;
24 : class nsInProcessTabChildGlobal;
25 : class nsWindowRoot;
26 :
27 : #define NS_WRAPPERCACHE_IID \
28 : { 0x6f3179a1, 0x36f7, 0x4a5c, \
29 : { 0x8c, 0xf1, 0xad, 0xc8, 0x7c, 0xde, 0x3e, 0x87 } }
30 :
31 : // There are two sets of flags used by DOM nodes. One comes from reusing the
32 : // remaining bits of the inherited nsWrapperCache flags (mFlags), and another is
33 : // exclusive to nsINode (mBoolFlags).
34 : //
35 : // Both sets of flags are 32 bits. On 64-bit platforms, this can cause two
36 : // wasted 32-bit fields due to alignment requirements. Some compilers are
37 : // smart enough to coalesce the fields if we make mBoolFlags the first member
38 : // of nsINode, but others (such as MSVC) are not.
39 : //
40 : // So we just store mBoolFlags directly on nsWrapperCache on 64-bit platforms.
41 : // This may waste space for some other nsWrapperCache-derived objects that have
42 : // a 32-bit field as their first member, but those objects are unlikely to be as
43 : // numerous or performance-critical as DOM nodes.
44 : #if defined(_M_X64) || defined(__LP64__)
45 : static_assert(sizeof(void*) == 8, "These architectures should be 64-bit");
46 : #define BOOL_FLAGS_ON_WRAPPER_CACHE
47 : #else
48 : static_assert(sizeof(void*) == 4, "Only support 32-bit and 64-bit");
49 : #endif
50 :
51 : /**
52 : * Class to store the wrapper for an object. This can only be used with objects
53 : * that only have one non-security wrapper at a time (for an XPCWrappedNative
54 : * this is usually ensured by setting an explicit parent in the PreCreate hook
55 : * for the class).
56 : *
57 : * An instance of nsWrapperCache can be gotten from an object that implements
58 : * a wrapper cache by calling QueryInterface on it. Note that this breaks XPCOM
59 : * rules a bit (this object doesn't derive from nsISupports).
60 : *
61 : * The cache can store objects other than wrappers. We allow wrappers to use a
62 : * separate JSObject to store their state (mostly expandos). If the wrapper is
63 : * collected and we want to preserve this state we actually store the state
64 : * object in the cache.
65 : *
66 : * The cache can store 2 types of objects:
67 : *
68 : * If WRAPPER_IS_NOT_DOM_BINDING is set (IsDOMBinding() returns false):
69 : * - the JSObject of an XPCWrappedNative wrapper
70 : *
71 : * If WRAPPER_IS_NOT_DOM_BINDING is not set (IsDOMBinding() returns true):
72 : * - a DOM binding object (regular JS object or proxy)
73 : *
74 : * The finalizer for the wrapper clears the cache.
75 : *
76 : * A compacting GC can move the wrapper object. Pointers to moved objects are
77 : * usually found and updated by tracing the heap, however non-preserved wrappers
78 : * are weak references and are not traced, so another approach is
79 : * necessary. Instead a class hook (objectMovedOp) is provided that is called
80 : * when an object is moved and is responsible for ensuring pointers are
81 : * updated. It does this by calling UpdateWrapper() on the wrapper
82 : * cache. SetWrapper() asserts that the hook is implemented for any wrapper set.
83 : *
84 : * A number of the methods are implemented in nsWrapperCacheInlines.h because we
85 : * have to include some JS headers that don't play nicely with the rest of the
86 : * codebase. Include nsWrapperCacheInlines.h if you need to call those methods.
87 : */
88 :
89 0 : class nsWrapperCache
90 : {
91 : public:
92 : NS_DECLARE_STATIC_IID_ACCESSOR(NS_WRAPPERCACHE_IID)
93 :
94 9960 : nsWrapperCache()
95 9960 : : mWrapper(nullptr)
96 : , mFlags(0)
97 : #ifdef BOOL_FLAGS_ON_WRAPPER_CACHE
98 9960 : , mBoolFlags(0)
99 : #endif
100 : {
101 9960 : }
102 332 : ~nsWrapperCache()
103 664 : {
104 332 : MOZ_ASSERT(!PreservingWrapper(),
105 : "Destroying cache with a preserved wrapper!");
106 332 : }
107 :
108 : /**
109 : * Get the cached wrapper.
110 : *
111 : * This getter clears the gray bit before handing out the JSObject which means
112 : * that the object is guaranteed to be kept alive past the next CC.
113 : */
114 : JSObject* GetWrapper() const;
115 :
116 : /**
117 : * Get the cached wrapper.
118 : *
119 : * This getter does not change the color of the JSObject meaning that the
120 : * object returned is not guaranteed to be kept alive past the next CC.
121 : *
122 : * This should only be called if you are certain that the return value won't
123 : * be passed into a JSAPI function and that it won't be stored without being
124 : * rooted (or otherwise signaling the stored value to the CC).
125 : */
126 : JSObject* GetWrapperPreserveColor() const;
127 :
128 : /**
129 : * Get the cached wrapper.
130 : *
131 : * This getter does not check whether the wrapper is dead and in the process
132 : * of being finalized.
133 : *
134 : * This should only be called if you really need to see the raw contents of
135 : * this cache, for example as part of finalization. Don't store the result
136 : * anywhere or pass it into JSAPI functions that may cause the value to
137 : * escape.
138 : */
139 215 : JSObject* GetWrapperMaybeDead() const
140 : {
141 215 : return mWrapper;
142 : }
143 :
144 : #ifdef DEBUG
145 : private:
146 : static bool HasJSObjectMovedOp(JSObject* aWrapper);
147 :
148 : public:
149 : #endif
150 :
151 2598 : void SetWrapper(JSObject* aWrapper)
152 : {
153 2598 : MOZ_ASSERT(!PreservingWrapper(), "Clearing a preserved wrapper!");
154 2598 : MOZ_ASSERT(aWrapper, "Use ClearWrapper!");
155 2598 : MOZ_ASSERT(HasJSObjectMovedOp(aWrapper),
156 : "Object has not provided the hook to update the wrapper if it is moved");
157 :
158 2598 : SetWrapperJSObject(aWrapper);
159 2598 : }
160 :
161 : /**
162 : * Clear the cache.
163 : */
164 58 : void ClearWrapper()
165 : {
166 58 : MOZ_ASSERT(!PreservingWrapper(), "Clearing a preserved wrapper!");
167 58 : SetWrapperJSObject(nullptr);
168 58 : }
169 :
170 : /**
171 : * Clear the cache if it still contains a specific wrapper object. This should
172 : * be called from the finalizer for the wrapper.
173 : */
174 58 : void ClearWrapper(JSObject* obj)
175 : {
176 58 : if (obj == mWrapper) {
177 58 : ClearWrapper();
178 : }
179 58 : }
180 :
181 : /**
182 : * Update the wrapper if the object it contains is moved.
183 : *
184 : * This method must be called from the objectMovedOp class extension hook for
185 : * any wrapper cached object.
186 : */
187 3 : void UpdateWrapper(JSObject* aNewObject, const JSObject* aOldObject)
188 : {
189 3 : if (mWrapper) {
190 3 : MOZ_ASSERT(mWrapper == aOldObject);
191 3 : mWrapper = aNewObject;
192 : }
193 3 : }
194 :
195 12172 : bool PreservingWrapper() const
196 : {
197 12172 : return HasWrapperFlag(WRAPPER_BIT_PRESERVED);
198 : }
199 :
200 5195 : bool IsDOMBinding() const
201 : {
202 5195 : return !HasWrapperFlag(WRAPPER_IS_NOT_DOM_BINDING);
203 : }
204 :
205 : /**
206 : * Wrap the object corresponding to this wrapper cache. If non-null is
207 : * returned, the object has already been stored in the wrapper cache.
208 : */
209 : virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) = 0;
210 :
211 : /**
212 : * Returns true if the object has a wrapper that is known live from the point
213 : * of view of cycle collection.
214 : */
215 : bool HasKnownLiveWrapper() const;
216 :
217 : /**
218 : * Returns true if the object has a known-live wrapper (from the CC point of
219 : * view) and all the GC things it is keeping alive are already known-live from
220 : * CC's point of view.
221 : */
222 : bool HasKnownLiveWrapperAndDoesNotNeedTracing(nsISupports* aThis);
223 :
224 : bool HasNothingToTrace(nsISupports* aThis);
225 :
226 : /**
227 : * Mark our wrapper, if any, as live as far as the CC is concerned.
228 : */
229 : void MarkWrapperLive();
230 :
231 : // Only meant to be called by code that preserves a wrapper.
232 4885 : void SetPreservingWrapper(bool aPreserve)
233 : {
234 4885 : if(aPreserve) {
235 2616 : SetWrapperFlags(WRAPPER_BIT_PRESERVED);
236 : }
237 : else {
238 2269 : UnsetWrapperFlags(WRAPPER_BIT_PRESERVED);
239 : }
240 4885 : }
241 :
242 5794 : void TraceWrapper(const TraceCallbacks& aCallbacks, void* aClosure)
243 : {
244 5794 : if (PreservingWrapper() && mWrapper) {
245 5396 : aCallbacks.Trace(&mWrapper, "Preserved wrapper", aClosure);
246 : }
247 5794 : }
248 :
249 : /*
250 : * The following methods for getting and manipulating flags allow the unused
251 : * bits of mFlags to be used by derived classes.
252 : */
253 :
254 : typedef uint32_t FlagsType;
255 :
256 211 : FlagsType GetFlags() const
257 : {
258 211 : return mFlags & ~kWrapperFlagsMask;
259 : }
260 :
261 613044 : bool HasFlag(FlagsType aFlag) const
262 : {
263 613044 : MOZ_ASSERT((aFlag & kWrapperFlagsMask) == 0, "Bad flag mask");
264 613044 : return !!(mFlags & aFlag);
265 : }
266 :
267 3250 : void SetFlags(FlagsType aFlagsToSet)
268 : {
269 3250 : MOZ_ASSERT((aFlagsToSet & kWrapperFlagsMask) == 0, "Bad flag mask");
270 3250 : mFlags |= aFlagsToSet;
271 3250 : }
272 :
273 16972 : void UnsetFlags(FlagsType aFlagsToUnset)
274 : {
275 16972 : MOZ_ASSERT((aFlagsToUnset & kWrapperFlagsMask) == 0, "Bad flag mask");
276 16972 : mFlags &= ~aFlagsToUnset;
277 16972 : }
278 :
279 713 : void PreserveWrapper(nsISupports* aScriptObjectHolder)
280 : {
281 713 : if (PreservingWrapper()) {
282 454 : return;
283 : }
284 :
285 : nsISupports* ccISupports;
286 : aScriptObjectHolder->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
287 259 : reinterpret_cast<void**>(&ccISupports));
288 259 : MOZ_ASSERT(ccISupports);
289 :
290 : nsXPCOMCycleCollectionParticipant* participant;
291 259 : CallQueryInterface(ccISupports, &participant);
292 259 : PreserveWrapper(ccISupports, participant);
293 : }
294 :
295 259 : void PreserveWrapper(void* aScriptObjectHolder, nsScriptObjectTracer* aTracer)
296 : {
297 259 : if (PreservingWrapper()) {
298 0 : return;
299 : }
300 :
301 259 : HoldJSObjects(aScriptObjectHolder, aTracer);
302 259 : SetPreservingWrapper(true);
303 : #ifdef DEBUG
304 : // Make sure the cycle collector will be able to traverse to the wrapper.
305 259 : CheckCCWrapperTraversal(aScriptObjectHolder, aTracer);
306 : #endif
307 : }
308 :
309 : void ReleaseWrapper(void* aScriptObjectHolder);
310 :
311 : protected:
312 3 : void TraceWrapper(JSTracer* aTrc, const char* name)
313 : {
314 3 : if (mWrapper) {
315 3 : js::UnsafeTraceManuallyBarrieredEdge(aTrc, &mWrapper, name);
316 : }
317 3 : }
318 :
319 0 : void PoisonWrapper()
320 : {
321 0 : if (mWrapper) {
322 : // Set the pointer to a value that will cause a crash if it is
323 : // dereferenced.
324 0 : mWrapper = reinterpret_cast<JSObject*>(1);
325 : }
326 0 : }
327 :
328 : private:
329 : // Friend declarations for things that need to be able to call
330 : // SetIsNotDOMBinding(). The goal is to get rid of all of these, and
331 : // SetIsNotDOMBinding() too.
332 : friend class mozilla::dom::TabChildGlobal;
333 : friend class mozilla::dom::ProcessGlobal;
334 : friend class SandboxPrivate;
335 : friend class nsInProcessTabChildGlobal;
336 : friend class nsWindowRoot;
337 30 : void SetIsNotDOMBinding()
338 : {
339 30 : MOZ_ASSERT(!mWrapper && !(GetWrapperFlags() & ~WRAPPER_IS_NOT_DOM_BINDING),
340 : "This flag should be set before creating any wrappers.");
341 30 : SetWrapperFlags(WRAPPER_IS_NOT_DOM_BINDING);
342 30 : }
343 :
344 : void SetWrapperJSObject(JSObject* aWrapper);
345 :
346 30 : FlagsType GetWrapperFlags() const
347 : {
348 30 : return mFlags & kWrapperFlagsMask;
349 : }
350 :
351 17367 : bool HasWrapperFlag(FlagsType aFlag) const
352 : {
353 17367 : MOZ_ASSERT((aFlag & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
354 17367 : return !!(mFlags & aFlag);
355 : }
356 :
357 2646 : void SetWrapperFlags(FlagsType aFlagsToSet)
358 : {
359 2646 : MOZ_ASSERT((aFlagsToSet & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
360 2646 : mFlags |= aFlagsToSet;
361 2646 : }
362 :
363 4925 : void UnsetWrapperFlags(FlagsType aFlagsToUnset)
364 : {
365 4925 : MOZ_ASSERT((aFlagsToUnset & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
366 4925 : mFlags &= ~aFlagsToUnset;
367 4925 : }
368 :
369 : void HoldJSObjects(void* aScriptObjectHolder,
370 : nsScriptObjectTracer* aTracer);
371 :
372 : #ifdef DEBUG
373 : public:
374 : void CheckCCWrapperTraversal(void* aScriptObjectHolder,
375 : nsScriptObjectTracer* aTracer);
376 : private:
377 : #endif // DEBUG
378 :
379 : /**
380 : * If this bit is set then we're preserving the wrapper, which in effect ties
381 : * the lifetime of the JS object stored in the cache to the lifetime of the
382 : * native object. We rely on the cycle collector to break the cycle that this
383 : * causes between the native object and the JS object, so it is important that
384 : * any native object that supports preserving of its wrapper
385 : * traces/traverses/unlinks the cached JS object (see
386 : * NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER and
387 : * NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER).
388 : */
389 : enum { WRAPPER_BIT_PRESERVED = 1 << 0 };
390 :
391 : /**
392 : * If this bit is set then the wrapper for the native object is not a DOM
393 : * binding.
394 : */
395 : enum { WRAPPER_IS_NOT_DOM_BINDING = 1 << 1 };
396 :
397 : enum { kWrapperFlagsMask = (WRAPPER_BIT_PRESERVED | WRAPPER_IS_NOT_DOM_BINDING) };
398 :
399 : JSObject* mWrapper;
400 : FlagsType mFlags;
401 : protected:
402 : #ifdef BOOL_FLAGS_ON_WRAPPER_CACHE
403 : uint32_t mBoolFlags;
404 : #endif
405 : };
406 :
407 : enum { WRAPPER_CACHE_FLAGS_BITS_USED = 2 };
408 :
409 : NS_DEFINE_STATIC_IID_ACCESSOR(nsWrapperCache, NS_WRAPPERCACHE_IID)
410 :
411 : #define NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY \
412 : if ( aIID.Equals(NS_GET_IID(nsWrapperCache)) ) { \
413 : *aInstancePtr = static_cast<nsWrapperCache*>(this); \
414 : return NS_OK; \
415 : }
416 :
417 : #define NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY \
418 : NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY \
419 : else
420 :
421 :
422 : // Cycle collector macros for wrapper caches.
423 :
424 : #define NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER \
425 : tmp->TraceWrapper(aCallbacks, aClosure);
426 :
427 : #define NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
428 : tmp->ReleaseWrapper(p);
429 :
430 : #define NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(_class) \
431 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \
432 : NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER \
433 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
434 :
435 : #define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(_class) \
436 : NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \
437 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
438 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
439 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
440 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
441 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
442 : NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(_class)
443 :
444 : #define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(_class, ...) \
445 : NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \
446 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
447 : NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
448 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
449 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
450 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
451 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
452 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
453 : NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(_class)
454 :
455 : #endif /* nsWrapperCache_h___ */
|