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 3 types of objects: a DOM binding object (regular JS object or
67 : * proxy), an nsOuterWindowProxy or an XPCWrappedNative wrapper.
68 : *
69 : * The finalizer for the wrapper clears the cache.
70 : *
71 : * A compacting GC can move the wrapper object. Pointers to moved objects are
72 : * usually found and updated by tracing the heap, however non-preserved wrappers
73 : * are weak references and are not traced, so another approach is
74 : * necessary. Instead a class hook (objectMovedOp) is provided that is called
75 : * when an object is moved and is responsible for ensuring pointers are
76 : * updated. It does this by calling UpdateWrapper() on the wrapper
77 : * cache. SetWrapper() asserts that the hook is implemented for any wrapper set.
78 : *
79 : * A number of the methods are implemented in nsWrapperCacheInlines.h because we
80 : * have to include some JS headers that don't play nicely with the rest of the
81 : * codebase. Include nsWrapperCacheInlines.h if you need to call those methods.
82 : */
83 :
84 : class nsWrapperCache
85 : {
86 : public:
87 : NS_DECLARE_STATIC_IID_ACCESSOR(NS_WRAPPERCACHE_IID)
88 :
89 : nsWrapperCache()
90 0 : : mWrapper(nullptr)
91 : , mFlags(0)
92 : #ifdef BOOL_FLAGS_ON_WRAPPER_CACHE
93 0 : , mBoolFlags(0)
94 : #endif
95 : {
96 : }
97 0 : ~nsWrapperCache()
98 0 : {
99 0 : MOZ_ASSERT(!PreservingWrapper(),
100 : "Destroying cache with a preserved wrapper!");
101 0 : }
102 :
103 : /**
104 : * Get the cached wrapper.
105 : *
106 : * This getter clears the gray bit before handing out the JSObject which means
107 : * that the object is guaranteed to be kept alive past the next CC.
108 : */
109 : JSObject* GetWrapper() const;
110 :
111 : /**
112 : * Get the cached wrapper.
113 : *
114 : * This getter does not change the color of the JSObject meaning that the
115 : * object returned is not guaranteed to be kept alive past the next CC.
116 : *
117 : * This should only be called if you are certain that the return value won't
118 : * be passed into a JSAPI function and that it won't be stored without being
119 : * rooted (or otherwise signaling the stored value to the CC).
120 : */
121 : JSObject* GetWrapperPreserveColor() const;
122 :
123 : /**
124 : * Get the cached wrapper.
125 : *
126 : * This getter does not check whether the wrapper is dead and in the process
127 : * of being finalized.
128 : *
129 : * This should only be called if you really need to see the raw contents of
130 : * this cache, for example as part of finalization. Don't store the result
131 : * anywhere or pass it into JSAPI functions that may cause the value to
132 : * escape.
133 : */
134 : JSObject* GetWrapperMaybeDead() const
135 : {
136 : return mWrapper;
137 : }
138 :
139 : #ifdef DEBUG
140 : private:
141 : static bool HasJSObjectMovedOp(JSObject* aWrapper);
142 :
143 : public:
144 : #endif
145 :
146 0 : void SetWrapper(JSObject* aWrapper)
147 : {
148 0 : MOZ_ASSERT(!PreservingWrapper(), "Clearing a preserved wrapper!");
149 0 : MOZ_ASSERT(aWrapper, "Use ClearWrapper!");
150 0 : MOZ_ASSERT(HasJSObjectMovedOp(aWrapper),
151 : "Object has not provided the hook to update the wrapper if it is moved");
152 :
153 0 : SetWrapperJSObject(aWrapper);
154 0 : }
155 :
156 : /**
157 : * Clear the cache.
158 : */
159 0 : void ClearWrapper()
160 : {
161 0 : MOZ_ASSERT(!PreservingWrapper(), "Clearing a preserved wrapper!");
162 0 : SetWrapperJSObject(nullptr);
163 0 : }
164 :
165 : /**
166 : * Clear the cache if it still contains a specific wrapper object. This should
167 : * be called from the finalizer for the wrapper.
168 : */
169 0 : void ClearWrapper(JSObject* obj)
170 : {
171 0 : if (obj == mWrapper) {
172 0 : ClearWrapper();
173 : }
174 0 : }
175 :
176 : /**
177 : * Update the wrapper if the object it contains is moved.
178 : *
179 : * This method must be called from the objectMovedOp class extension hook for
180 : * any wrapper cached object.
181 : */
182 0 : void UpdateWrapper(JSObject* aNewObject, const JSObject* aOldObject)
183 : {
184 0 : if (mWrapper) {
185 0 : MOZ_ASSERT(mWrapper == aOldObject);
186 0 : mWrapper = aNewObject;
187 : }
188 0 : }
189 :
190 0 : bool PreservingWrapper() const
191 : {
192 0 : return HasWrapperFlag(WRAPPER_BIT_PRESERVED);
193 : }
194 :
195 : /**
196 : * Wrap the object corresponding to this wrapper cache. If non-null is
197 : * returned, the object has already been stored in the wrapper cache.
198 : */
199 : virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) = 0;
200 :
201 : /**
202 : * Returns true if the object has a wrapper that is known live from the point
203 : * of view of cycle collection.
204 : */
205 : bool HasKnownLiveWrapper() const;
206 :
207 : /**
208 : * Returns true if the object has a known-live wrapper (from the CC point of
209 : * view) and all the GC things it is keeping alive are already known-live from
210 : * CC's point of view.
211 : */
212 : bool HasKnownLiveWrapperAndDoesNotNeedTracing(nsISupports* aThis);
213 :
214 : bool HasNothingToTrace(nsISupports* aThis);
215 :
216 : /**
217 : * Mark our wrapper, if any, as live as far as the CC is concerned.
218 : */
219 : void MarkWrapperLive();
220 :
221 : // Only meant to be called by code that preserves a wrapper.
222 0 : void SetPreservingWrapper(bool aPreserve)
223 : {
224 0 : if(aPreserve) {
225 0 : SetWrapperFlags(WRAPPER_BIT_PRESERVED);
226 : }
227 : else {
228 0 : UnsetWrapperFlags(WRAPPER_BIT_PRESERVED);
229 : }
230 0 : }
231 :
232 0 : void TraceWrapper(const TraceCallbacks& aCallbacks, void* aClosure)
233 : {
234 0 : if (PreservingWrapper() && mWrapper) {
235 0 : aCallbacks.Trace(&mWrapper, "Preserved wrapper", aClosure);
236 : }
237 0 : }
238 :
239 : /*
240 : * The following methods for getting and manipulating flags allow the unused
241 : * bits of mFlags to be used by derived classes.
242 : */
243 :
244 : typedef uint32_t FlagsType;
245 :
246 : FlagsType GetFlags() const
247 : {
248 0 : return mFlags & ~kWrapperFlagsMask;
249 : }
250 :
251 0 : bool HasFlag(FlagsType aFlag) const
252 : {
253 0 : MOZ_ASSERT((aFlag & kWrapperFlagsMask) == 0, "Bad flag mask");
254 0 : return !!(mFlags & aFlag);
255 : }
256 :
257 : // Identical to HasFlag, but more explicit about its handling of multiple
258 : // flags.
259 0 : bool HasAnyOfFlags(FlagsType aFlags) const
260 : {
261 0 : MOZ_ASSERT((aFlags & kWrapperFlagsMask) == 0, "Bad flag mask");
262 0 : return !!(mFlags & aFlags);
263 : }
264 :
265 0 : bool HasAllFlags(FlagsType aFlags) const
266 : {
267 0 : MOZ_ASSERT((aFlags & kWrapperFlagsMask) == 0, "Bad flag mask");
268 0 : return (mFlags & aFlags) == aFlags;
269 : }
270 :
271 0 : void SetFlags(FlagsType aFlagsToSet)
272 : {
273 0 : MOZ_ASSERT((aFlagsToSet & kWrapperFlagsMask) == 0, "Bad flag mask");
274 0 : mFlags |= aFlagsToSet;
275 0 : }
276 :
277 0 : void UnsetFlags(FlagsType aFlagsToUnset)
278 : {
279 0 : MOZ_ASSERT((aFlagsToUnset & kWrapperFlagsMask) == 0, "Bad flag mask");
280 0 : mFlags &= ~aFlagsToUnset;
281 0 : }
282 :
283 0 : void PreserveWrapper(nsISupports* aScriptObjectHolder)
284 : {
285 0 : if (PreservingWrapper()) {
286 0 : return;
287 : }
288 :
289 : nsISupports* ccISupports;
290 : aScriptObjectHolder->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
291 0 : reinterpret_cast<void**>(&ccISupports));
292 0 : MOZ_ASSERT(ccISupports);
293 :
294 : nsXPCOMCycleCollectionParticipant* participant;
295 0 : CallQueryInterface(ccISupports, &participant);
296 0 : PreserveWrapper(ccISupports, participant);
297 : }
298 :
299 0 : void PreserveWrapper(void* aScriptObjectHolder, nsScriptObjectTracer* aTracer)
300 : {
301 0 : if (PreservingWrapper()) {
302 : return;
303 : }
304 :
305 0 : GetWrapper(); // Read barrier for incremental GC.
306 0 : HoldJSObjects(aScriptObjectHolder, aTracer);
307 0 : SetPreservingWrapper(true);
308 : #ifdef DEBUG
309 : // Make sure the cycle collector will be able to traverse to the wrapper.
310 0 : CheckCCWrapperTraversal(aScriptObjectHolder, aTracer);
311 : #endif
312 : }
313 :
314 : void ReleaseWrapper(void* aScriptObjectHolder);
315 :
316 : protected:
317 0 : void TraceWrapper(JSTracer* aTrc, const char* name)
318 : {
319 0 : if (mWrapper) {
320 0 : js::UnsafeTraceManuallyBarrieredEdge(aTrc, &mWrapper, name);
321 : }
322 0 : }
323 :
324 0 : void PoisonWrapper()
325 : {
326 0 : if (mWrapper) {
327 : // Set the pointer to a value that will cause a crash if it is
328 : // dereferenced.
329 0 : mWrapper = reinterpret_cast<JSObject*>(1);
330 : }
331 0 : }
332 :
333 : private:
334 : void SetWrapperJSObject(JSObject* aWrapper);
335 :
336 : FlagsType GetWrapperFlags() const
337 : {
338 : return mFlags & kWrapperFlagsMask;
339 : }
340 :
341 0 : bool HasWrapperFlag(FlagsType aFlag) const
342 : {
343 0 : MOZ_ASSERT((aFlag & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
344 0 : return !!(mFlags & aFlag);
345 : }
346 :
347 0 : void SetWrapperFlags(FlagsType aFlagsToSet)
348 : {
349 0 : MOZ_ASSERT((aFlagsToSet & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
350 0 : mFlags |= aFlagsToSet;
351 0 : }
352 :
353 0 : void UnsetWrapperFlags(FlagsType aFlagsToUnset)
354 : {
355 0 : MOZ_ASSERT((aFlagsToUnset & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
356 0 : mFlags &= ~aFlagsToUnset;
357 0 : }
358 :
359 : void HoldJSObjects(void* aScriptObjectHolder,
360 : nsScriptObjectTracer* aTracer);
361 :
362 : #ifdef DEBUG
363 : public:
364 : void CheckCCWrapperTraversal(void* aScriptObjectHolder,
365 : nsScriptObjectTracer* aTracer);
366 : private:
367 : #endif // DEBUG
368 :
369 : /**
370 : * If this bit is set then we're preserving the wrapper, which in effect ties
371 : * the lifetime of the JS object stored in the cache to the lifetime of the
372 : * native object. We rely on the cycle collector to break the cycle that this
373 : * causes between the native object and the JS object, so it is important that
374 : * any native object that supports preserving of its wrapper
375 : * traces/traverses/unlinks the cached JS object (see
376 : * NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER and
377 : * NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER).
378 : */
379 : enum { WRAPPER_BIT_PRESERVED = 1 << 0 };
380 :
381 : enum { kWrapperFlagsMask = WRAPPER_BIT_PRESERVED };
382 :
383 : JSObject* mWrapper;
384 : FlagsType mFlags;
385 : protected:
386 : #ifdef BOOL_FLAGS_ON_WRAPPER_CACHE
387 : uint32_t mBoolFlags;
388 : #endif
389 : };
390 :
391 : enum { WRAPPER_CACHE_FLAGS_BITS_USED = 1 };
392 :
393 : NS_DEFINE_STATIC_IID_ACCESSOR(nsWrapperCache, NS_WRAPPERCACHE_IID)
394 :
395 : #define NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY \
396 : if ( aIID.Equals(NS_GET_IID(nsWrapperCache)) ) { \
397 : *aInstancePtr = static_cast<nsWrapperCache*>(this); \
398 : return NS_OK; \
399 : }
400 :
401 : #define NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY \
402 : NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY \
403 : else
404 :
405 :
406 : // Cycle collector macros for wrapper caches.
407 :
408 : #define NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER \
409 : tmp->TraceWrapper(aCallbacks, aClosure);
410 :
411 : #define NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
412 : tmp->ReleaseWrapper(p);
413 :
414 : #define NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(_class) \
415 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \
416 : NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER \
417 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
418 :
419 : #define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(_class) \
420 : NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \
421 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
422 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
423 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
424 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
425 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
426 : NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(_class)
427 :
428 : #define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(_class, ...) \
429 : NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \
430 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
431 : NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
432 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
433 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
434 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
435 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
436 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
437 : NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(_class)
438 :
439 : #endif /* nsWrapperCache_h___ */
|