|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
|
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 /** |
|
8 * A common base class for representing WebIDL callback function and |
|
9 * callback interface types in C++. |
|
10 * |
|
11 * This class implements common functionality like lifetime |
|
12 * management, initialization with the JS object, and setup of the |
|
13 * call environment. Subclasses are responsible for providing methods |
|
14 * that do the call into JS as needed. |
|
15 */ |
|
16 |
|
17 #ifndef mozilla_dom_CallbackObject_h |
|
18 #define mozilla_dom_CallbackObject_h |
|
19 |
|
20 #include "nsISupports.h" |
|
21 #include "nsISupportsImpl.h" |
|
22 #include "nsCycleCollectionParticipant.h" |
|
23 #include "jswrapper.h" |
|
24 #include "mozilla/Assertions.h" |
|
25 #include "mozilla/ErrorResult.h" |
|
26 #include "mozilla/HoldDropJSObjects.h" |
|
27 #include "mozilla/MemoryReporting.h" |
|
28 #include "mozilla/dom/ScriptSettings.h" |
|
29 #include "nsContentUtils.h" |
|
30 #include "nsWrapperCache.h" |
|
31 #include "nsJSEnvironment.h" |
|
32 #include "xpcpublic.h" |
|
33 |
|
34 namespace mozilla { |
|
35 namespace dom { |
|
36 |
|
37 #define DOM_CALLBACKOBJECT_IID \ |
|
38 { 0xbe74c190, 0x6d76, 0x4991, \ |
|
39 { 0x84, 0xb9, 0x65, 0x06, 0x99, 0xe6, 0x93, 0x2b } } |
|
40 |
|
41 class CallbackObject : public nsISupports |
|
42 { |
|
43 public: |
|
44 NS_DECLARE_STATIC_IID_ACCESSOR(DOM_CALLBACKOBJECT_IID) |
|
45 |
|
46 NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
|
47 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackObject) |
|
48 |
|
49 // The caller may pass a global object which will act as an override for the |
|
50 // incumbent script settings object when the callback is invoked (overriding |
|
51 // the entry point computed from aCallback). If no override is required, the |
|
52 // caller should pass null. |
|
53 explicit CallbackObject(JS::Handle<JSObject*> aCallback, nsIGlobalObject *aIncumbentGlobal) |
|
54 { |
|
55 Init(aCallback, aIncumbentGlobal); |
|
56 } |
|
57 |
|
58 virtual ~CallbackObject() |
|
59 { |
|
60 DropJSObjects(); |
|
61 } |
|
62 |
|
63 JS::Handle<JSObject*> Callback() const |
|
64 { |
|
65 JS::ExposeObjectToActiveJS(mCallback); |
|
66 return CallbackPreserveColor(); |
|
67 } |
|
68 |
|
69 /* |
|
70 * This getter does not change the color of the JSObject meaning that the |
|
71 * object returned is not guaranteed to be kept alive past the next CC. |
|
72 * |
|
73 * This should only be called if you are certain that the return value won't |
|
74 * be passed into a JS API function and that it won't be stored without being |
|
75 * rooted (or otherwise signaling the stored value to the CC). |
|
76 */ |
|
77 JS::Handle<JSObject*> CallbackPreserveColor() const |
|
78 { |
|
79 // Calling fromMarkedLocation() is safe because we trace our mCallback, and |
|
80 // because the value of mCallback cannot change after if has been set. |
|
81 return JS::Handle<JSObject*>::fromMarkedLocation(mCallback.address()); |
|
82 } |
|
83 |
|
84 nsIGlobalObject* IncumbentGlobalOrNull() const |
|
85 { |
|
86 return mIncumbentGlobal; |
|
87 } |
|
88 |
|
89 enum ExceptionHandling { |
|
90 // Report any exception and don't throw it to the caller code. |
|
91 eReportExceptions, |
|
92 // Throw an exception to the caller code if the thrown exception is a |
|
93 // binding object for a DOMError from the caller's scope, otherwise report |
|
94 // it. |
|
95 eRethrowContentExceptions, |
|
96 // Throw any exception to the caller code. |
|
97 eRethrowExceptions |
|
98 }; |
|
99 |
|
100 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
|
101 { |
|
102 return aMallocSizeOf(this); |
|
103 } |
|
104 |
|
105 protected: |
|
106 explicit CallbackObject(CallbackObject* aCallbackObject) |
|
107 { |
|
108 Init(aCallbackObject->mCallback, aCallbackObject->mIncumbentGlobal); |
|
109 } |
|
110 |
|
111 bool operator==(const CallbackObject& aOther) const |
|
112 { |
|
113 JSObject* thisObj = |
|
114 js::UncheckedUnwrap(CallbackPreserveColor()); |
|
115 JSObject* otherObj = |
|
116 js::UncheckedUnwrap(aOther.CallbackPreserveColor()); |
|
117 return thisObj == otherObj; |
|
118 } |
|
119 |
|
120 private: |
|
121 inline void Init(JSObject* aCallback, nsIGlobalObject* aIncumbentGlobal) |
|
122 { |
|
123 MOZ_ASSERT(aCallback && !mCallback); |
|
124 // Set script objects before we hold, on the off chance that a GC could |
|
125 // somehow happen in there... (which would be pretty odd, granted). |
|
126 mCallback = aCallback; |
|
127 if (aIncumbentGlobal) { |
|
128 mIncumbentGlobal = aIncumbentGlobal; |
|
129 mIncumbentJSGlobal = aIncumbentGlobal->GetGlobalJSObject(); |
|
130 } |
|
131 mozilla::HoldJSObjects(this); |
|
132 } |
|
133 |
|
134 CallbackObject(const CallbackObject&) MOZ_DELETE; |
|
135 CallbackObject& operator =(const CallbackObject&) MOZ_DELETE; |
|
136 |
|
137 protected: |
|
138 void DropJSObjects() |
|
139 { |
|
140 MOZ_ASSERT_IF(mIncumbentJSGlobal, mCallback); |
|
141 if (mCallback) { |
|
142 mCallback = nullptr; |
|
143 mIncumbentJSGlobal = nullptr; |
|
144 mozilla::DropJSObjects(this); |
|
145 } |
|
146 } |
|
147 |
|
148 JS::Heap<JSObject*> mCallback; |
|
149 // Ideally, we'd just hold a reference to the nsIGlobalObject, since that's |
|
150 // what we need to pass to AutoIncumbentScript. Unfortunately, that doesn't |
|
151 // hold the actual JS global alive. So we maintain an additional pointer to |
|
152 // the JS global itself so that we can trace it. |
|
153 // |
|
154 // At some point we should consider trying to make native globals hold their |
|
155 // scripted global alive, at which point we can get rid of the duplication |
|
156 // here. |
|
157 nsCOMPtr<nsIGlobalObject> mIncumbentGlobal; |
|
158 JS::TenuredHeap<JSObject*> mIncumbentJSGlobal; |
|
159 |
|
160 class MOZ_STACK_CLASS CallSetup |
|
161 { |
|
162 /** |
|
163 * A class that performs whatever setup we need to safely make a |
|
164 * call while this class is on the stack, After the constructor |
|
165 * returns, the call is safe to make if GetContext() returns |
|
166 * non-null. |
|
167 */ |
|
168 public: |
|
169 // If aExceptionHandling == eRethrowContentExceptions then aCompartment |
|
170 // needs to be set to the compartment in which exceptions will be rethrown. |
|
171 CallSetup(CallbackObject* aCallback, ErrorResult& aRv, |
|
172 ExceptionHandling aExceptionHandling, |
|
173 JSCompartment* aCompartment = nullptr, |
|
174 bool aIsJSImplementedWebIDL = false); |
|
175 ~CallSetup(); |
|
176 |
|
177 JSContext* GetContext() const |
|
178 { |
|
179 return mCx; |
|
180 } |
|
181 |
|
182 private: |
|
183 // We better not get copy-constructed |
|
184 CallSetup(const CallSetup&) MOZ_DELETE; |
|
185 |
|
186 bool ShouldRethrowException(JS::Handle<JS::Value> aException); |
|
187 |
|
188 // Members which can go away whenever |
|
189 JSContext* mCx; |
|
190 |
|
191 // Caller's compartment. This will only have a sensible value if |
|
192 // mExceptionHandling == eRethrowContentExceptions. |
|
193 JSCompartment* mCompartment; |
|
194 |
|
195 // And now members whose construction/destruction order we need to control. |
|
196 Maybe<AutoEntryScript> mAutoEntryScript; |
|
197 Maybe<AutoIncumbentScript> mAutoIncumbentScript; |
|
198 |
|
199 // Constructed the rooter within the scope of mCxPusher above, so that it's |
|
200 // always within a request during its lifetime. |
|
201 Maybe<JS::Rooted<JSObject*> > mRootedCallable; |
|
202 |
|
203 // Can't construct a JSAutoCompartment without a JSContext either. Also, |
|
204 // Put mAc after mAutoEntryScript so that we exit the compartment before |
|
205 // we pop the JSContext. Though in practice we'll often manually order |
|
206 // those two things. |
|
207 Maybe<JSAutoCompartment> mAc; |
|
208 |
|
209 // An ErrorResult to possibly re-throw exceptions on and whether |
|
210 // we should re-throw them. |
|
211 ErrorResult& mErrorResult; |
|
212 const ExceptionHandling mExceptionHandling; |
|
213 JS::ContextOptions mSavedJSContextOptions; |
|
214 const bool mIsMainThread; |
|
215 }; |
|
216 }; |
|
217 |
|
218 template<class WebIDLCallbackT, class XPCOMCallbackT> |
|
219 class CallbackObjectHolder; |
|
220 |
|
221 template<class T, class U> |
|
222 void ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField); |
|
223 |
|
224 class CallbackObjectHolderBase |
|
225 { |
|
226 protected: |
|
227 // Returns null on all failures |
|
228 already_AddRefed<nsISupports> ToXPCOMCallback(CallbackObject* aCallback, |
|
229 const nsIID& aIID) const; |
|
230 }; |
|
231 |
|
232 template<class WebIDLCallbackT, class XPCOMCallbackT> |
|
233 class CallbackObjectHolder : CallbackObjectHolderBase |
|
234 { |
|
235 /** |
|
236 * A class which stores either a WebIDLCallbackT* or an XPCOMCallbackT*. Both |
|
237 * types must inherit from nsISupports. The pointer that's stored can be |
|
238 * null. |
|
239 * |
|
240 * When storing a WebIDLCallbackT*, mPtrBits is set to the pointer value. |
|
241 * When storing an XPCOMCallbackT*, mPtrBits is the pointer value with low bit |
|
242 * set. |
|
243 */ |
|
244 public: |
|
245 explicit CallbackObjectHolder(WebIDLCallbackT* aCallback) |
|
246 : mPtrBits(reinterpret_cast<uintptr_t>(aCallback)) |
|
247 { |
|
248 NS_IF_ADDREF(aCallback); |
|
249 } |
|
250 |
|
251 explicit CallbackObjectHolder(XPCOMCallbackT* aCallback) |
|
252 : mPtrBits(reinterpret_cast<uintptr_t>(aCallback) | XPCOMCallbackFlag) |
|
253 { |
|
254 NS_IF_ADDREF(aCallback); |
|
255 } |
|
256 |
|
257 explicit CallbackObjectHolder(const CallbackObjectHolder& aOther) |
|
258 : mPtrBits(aOther.mPtrBits) |
|
259 { |
|
260 NS_IF_ADDREF(GetISupports()); |
|
261 } |
|
262 |
|
263 CallbackObjectHolder() |
|
264 : mPtrBits(0) |
|
265 {} |
|
266 |
|
267 ~CallbackObjectHolder() |
|
268 { |
|
269 UnlinkSelf(); |
|
270 } |
|
271 |
|
272 void operator=(WebIDLCallbackT* aCallback) |
|
273 { |
|
274 UnlinkSelf(); |
|
275 mPtrBits = reinterpret_cast<uintptr_t>(aCallback); |
|
276 NS_IF_ADDREF(aCallback); |
|
277 } |
|
278 |
|
279 void operator=(XPCOMCallbackT* aCallback) |
|
280 { |
|
281 UnlinkSelf(); |
|
282 mPtrBits = reinterpret_cast<uintptr_t>(aCallback) | XPCOMCallbackFlag; |
|
283 NS_IF_ADDREF(aCallback); |
|
284 } |
|
285 |
|
286 void operator=(const CallbackObjectHolder& aOther) |
|
287 { |
|
288 UnlinkSelf(); |
|
289 mPtrBits = aOther.mPtrBits; |
|
290 NS_IF_ADDREF(GetISupports()); |
|
291 } |
|
292 |
|
293 nsISupports* GetISupports() const |
|
294 { |
|
295 return reinterpret_cast<nsISupports*>(mPtrBits & ~XPCOMCallbackFlag); |
|
296 } |
|
297 |
|
298 // Boolean conversion operator so people can use this in boolean tests |
|
299 operator bool() const |
|
300 { |
|
301 return GetISupports(); |
|
302 } |
|
303 |
|
304 // Even if HasWebIDLCallback returns true, GetWebIDLCallback() might still |
|
305 // return null. |
|
306 bool HasWebIDLCallback() const |
|
307 { |
|
308 return !(mPtrBits & XPCOMCallbackFlag); |
|
309 } |
|
310 |
|
311 WebIDLCallbackT* GetWebIDLCallback() const |
|
312 { |
|
313 MOZ_ASSERT(HasWebIDLCallback()); |
|
314 return reinterpret_cast<WebIDLCallbackT*>(mPtrBits); |
|
315 } |
|
316 |
|
317 XPCOMCallbackT* GetXPCOMCallback() const |
|
318 { |
|
319 MOZ_ASSERT(!HasWebIDLCallback()); |
|
320 return reinterpret_cast<XPCOMCallbackT*>(mPtrBits & ~XPCOMCallbackFlag); |
|
321 } |
|
322 |
|
323 bool operator==(WebIDLCallbackT* aOtherCallback) const |
|
324 { |
|
325 if (!aOtherCallback) { |
|
326 // If other is null, then we must be null to be equal. |
|
327 return !GetISupports(); |
|
328 } |
|
329 |
|
330 if (!HasWebIDLCallback() || !GetWebIDLCallback()) { |
|
331 // If other is non-null, then we can't be equal if we have a |
|
332 // non-WebIDL callback or a null callback. |
|
333 return false; |
|
334 } |
|
335 |
|
336 return *GetWebIDLCallback() == *aOtherCallback; |
|
337 } |
|
338 |
|
339 bool operator==(XPCOMCallbackT* aOtherCallback) const |
|
340 { |
|
341 return (!aOtherCallback && !GetISupports()) || |
|
342 (!HasWebIDLCallback() && GetXPCOMCallback() == aOtherCallback); |
|
343 } |
|
344 |
|
345 bool operator==(const CallbackObjectHolder& aOtherCallback) const |
|
346 { |
|
347 if (aOtherCallback.HasWebIDLCallback()) { |
|
348 return *this == aOtherCallback.GetWebIDLCallback(); |
|
349 } |
|
350 |
|
351 return *this == aOtherCallback.GetXPCOMCallback(); |
|
352 } |
|
353 |
|
354 // Try to return an XPCOMCallbackT version of this object. |
|
355 already_AddRefed<XPCOMCallbackT> ToXPCOMCallback() const |
|
356 { |
|
357 if (!HasWebIDLCallback()) { |
|
358 nsRefPtr<XPCOMCallbackT> callback = GetXPCOMCallback(); |
|
359 return callback.forget(); |
|
360 } |
|
361 |
|
362 nsCOMPtr<nsISupports> supp = |
|
363 CallbackObjectHolderBase::ToXPCOMCallback(GetWebIDLCallback(), |
|
364 NS_GET_TEMPLATE_IID(XPCOMCallbackT)); |
|
365 // ToXPCOMCallback already did the right QI for us. |
|
366 return supp.forget().downcast<XPCOMCallbackT>(); |
|
367 } |
|
368 |
|
369 // Try to return a WebIDLCallbackT version of this object. |
|
370 already_AddRefed<WebIDLCallbackT> ToWebIDLCallback() const |
|
371 { |
|
372 if (HasWebIDLCallback()) { |
|
373 nsRefPtr<WebIDLCallbackT> callback = GetWebIDLCallback(); |
|
374 return callback.forget(); |
|
375 } |
|
376 return nullptr; |
|
377 } |
|
378 |
|
379 private: |
|
380 static const uintptr_t XPCOMCallbackFlag = 1u; |
|
381 |
|
382 friend void |
|
383 ImplCycleCollectionUnlink<WebIDLCallbackT, |
|
384 XPCOMCallbackT>(CallbackObjectHolder& aField); |
|
385 |
|
386 void UnlinkSelf() |
|
387 { |
|
388 // NS_IF_RELEASE because we might have been unlinked before |
|
389 nsISupports* ptr = GetISupports(); |
|
390 NS_IF_RELEASE(ptr); |
|
391 mPtrBits = 0; |
|
392 } |
|
393 |
|
394 uintptr_t mPtrBits; |
|
395 }; |
|
396 |
|
397 NS_DEFINE_STATIC_IID_ACCESSOR(CallbackObject, DOM_CALLBACKOBJECT_IID) |
|
398 |
|
399 template<class T, class U> |
|
400 inline void |
|
401 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, |
|
402 CallbackObjectHolder<T, U>& aField, |
|
403 const char* aName, |
|
404 uint32_t aFlags = 0) |
|
405 { |
|
406 CycleCollectionNoteChild(aCallback, aField.GetISupports(), aName, aFlags); |
|
407 } |
|
408 |
|
409 template<class T, class U> |
|
410 void |
|
411 ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField) |
|
412 { |
|
413 aField.UnlinkSelf(); |
|
414 } |
|
415 |
|
416 } // namespace dom |
|
417 } // namespace mozilla |
|
418 |
|
419 #endif // mozilla_dom_CallbackObject_h |