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 /* Class that wraps JS objects to appear as XPCOM objects. */
8
9 #include "xpcprivate.h"
10 #include "XPCMaps.h"
11 #include "mozilla/DeferredFinalize.h"
12 #include "mozilla/Sprintf.h"
13 #include "js/Object.h" // JS::GetCompartment
14 #include "js/RealmIterators.h"
15 #include "nsCCUncollectableMarker.h"
16 #include "nsContentUtils.h"
17 #include "nsThreadUtils.h"
18
19 using namespace mozilla;
20
21 // NOTE: much of the fancy footwork is done in xpcstubs.cpp
22
23 // nsXPCWrappedJS lifetime.
24 //
25 // An nsXPCWrappedJS is either rooting its JS object or is subject to
26 // finalization. The subject-to-finalization state lets wrappers support
27 // nsSupportsWeakReference in the case where the underlying JS object
28 // is strongly owned, but the wrapper itself is only weakly owned.
29 //
30 // A wrapper is rooting its JS object whenever its refcount is greater than 1.
31 // In this state, root wrappers are always added to the cycle collector graph.
32 // The wrapper keeps around an extra refcount, added in the constructor, to
33 // support the possibility of an eventual transition to the
34 // subject-to-finalization state. This extra refcount is ignored by the cycle
35 // collector, which traverses the "self" edge for this refcount.
36 //
37 // When the refcount of a rooting wrapper drops to 1, if there is no weak
38 // reference to the wrapper (which can only happen for the root wrapper), it is
39 // immediately Destroy()'d. Otherwise, it becomes subject to finalization.
40 //
41 // When a wrapper is subject to finalization, the wrapper has a refcount of 1.
42 // It is now owned exclusively by its JS object. Either a weak reference will be
43 // turned into a strong ref which will bring its refcount up to 2 and change the
44 // wrapper back to the rooting state, or it will stay alive until the JS object
45 // dies. If the JS object dies, then when
46 // JSObject2WrappedJSMap::UpdateWeakPointersAfterGC is called (via the JS
47 // engine's weak pointer zone or compartment callbacks) it will find the wrapper
48 // and call Release() on it, destroying the wrapper. Otherwise, the wrapper will
49 // stay alive, even if it no longer has a weak reference to it.
50 //
51 // When the wrapper is subject to finalization, it is kept alive by an implicit
52 // reference from the JS object which is invisible to the cycle collector, so
53 // the cycle collector does not traverse any children of wrappers that are
54 // subject to finalization. This will result in a leak if a wrapper in the
55 // non-rooting state has an aggregated native that keeps alive the wrapper's JS
56 // object. See bug 947049.
57
58 // If traversing wrappedJS wouldn't release it, nor cause any other objects to
59 // be added to the graph, there is no need to add it to the graph at all.
CanSkip()60 bool nsXPCWrappedJS::CanSkip() {
61 if (!nsCCUncollectableMarker::sGeneration) {
62 return false;
63 }
64
65 if (IsSubjectToFinalization()) {
66 return true;
67 }
68
69 // If this wrapper holds a gray object, need to trace it.
70 JSObject* obj = GetJSObjectPreserveColor();
71 if (obj && JS::ObjectIsMarkedGray(obj)) {
72 return false;
73 }
74
75 // For non-root wrappers, check if the root wrapper will be
76 // added to the CC graph.
77 if (!IsRootWrapper()) {
78 // mRoot points to null after unlinking.
79 NS_ENSURE_TRUE(mRoot, false);
80 return mRoot->CanSkip();
81 }
82
83 // For the root wrapper, check if there is an aggregated
84 // native object that will be added to the CC graph.
85 if (!IsAggregatedToNative()) {
86 return true;
87 }
88
89 nsISupports* agg = GetAggregatedNativeObject();
90 nsXPCOMCycleCollectionParticipant* cp = nullptr;
91 CallQueryInterface(agg, &cp);
92 nsISupports* canonical = nullptr;
93 agg->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
94 reinterpret_cast<void**>(&canonical));
95 return cp && canonical && cp->CanSkipThis(canonical);
96 }
97
98 NS_IMETHODIMP
NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)99 NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::TraverseNative(
100 void* p, nsCycleCollectionTraversalCallback& cb) {
101 nsISupports* s = static_cast<nsISupports*>(p);
102 MOZ_ASSERT(CheckForRightISupports(s),
103 "not the nsISupports pointer we expect");
104 nsXPCWrappedJS* tmp = Downcast(s);
105
106 nsrefcnt refcnt = tmp->mRefCnt.get();
107 if (cb.WantDebugInfo()) {
108 char name[72];
109 SprintfLiteral(name, "nsXPCWrappedJS (%s)", tmp->mInfo->Name());
110 cb.DescribeRefCountedNode(refcnt, name);
111 } else {
112 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsXPCWrappedJS, refcnt)
113 }
114
115 // A wrapper that is subject to finalization will only die when its JS object
116 // dies.
117 if (tmp->IsSubjectToFinalization()) {
118 return NS_OK;
119 }
120
121 // Don't let the extra reference for nsSupportsWeakReference keep a wrapper
122 // that is not subject to finalization alive.
123 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "self");
124 cb.NoteXPCOMChild(s);
125
126 if (tmp->IsValid()) {
127 MOZ_ASSERT(refcnt > 1);
128 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mJSObj");
129 cb.NoteJSChild(JS::GCCellPtr(tmp->GetJSObjectPreserveColor()));
130 }
131
132 if (tmp->IsRootWrapper()) {
133 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "aggregated native");
134 cb.NoteXPCOMChild(tmp->GetAggregatedNativeObject());
135 } else {
136 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "root");
137 cb.NoteXPCOMChild(ToSupports(tmp->GetRootWrapper()));
138 }
139
140 return NS_OK;
141 }
142
143 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXPCWrappedJS)
144
145 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXPCWrappedJS)
146 tmp->Unlink();
147 // Note: Unlink already calls ClearWeakReferences, so no need for
148 // NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE here.
149 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
150
151 // XPCJSContext keeps a table of WJS, so we can remove them from
152 // the purple buffer in between CCs.
153 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXPCWrappedJS)
154 return true;
155 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
156
157 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXPCWrappedJS)
158 return tmp->CanSkip();
159 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
160
161 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXPCWrappedJS)
162 return tmp->CanSkip();
163 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
164
165 NS_IMETHODIMP
AggregatedQueryInterface(REFNSIID aIID,void ** aInstancePtr)166 nsXPCWrappedJS::AggregatedQueryInterface(REFNSIID aIID, void** aInstancePtr) {
167 MOZ_ASSERT(IsAggregatedToNative(), "bad AggregatedQueryInterface call");
168 *aInstancePtr = nullptr;
169
170 if (!IsValid()) {
171 return NS_ERROR_UNEXPECTED;
172 }
173
174 // Put this here rather that in DelegatedQueryInterface because it needs
175 // to be in QueryInterface before the possible delegation to 'outer', but
176 // we don't want to do this check twice in one call in the normal case:
177 // once in QueryInterface and once in DelegatedQueryInterface.
178 if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJS))) {
179 NS_ADDREF(this);
180 *aInstancePtr = (void*)static_cast<nsIXPConnectWrappedJS*>(this);
181 return NS_OK;
182 }
183
184 return DelegatedQueryInterface(aIID, aInstancePtr);
185 }
186
187 NS_IMETHODIMP
QueryInterface(REFNSIID aIID,void ** aInstancePtr)188 nsXPCWrappedJS::QueryInterface(REFNSIID aIID, void** aInstancePtr) {
189 if (nullptr == aInstancePtr) {
190 MOZ_ASSERT(false, "null pointer");
191 return NS_ERROR_NULL_POINTER;
192 }
193
194 *aInstancePtr = nullptr;
195
196 if (aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant))) {
197 *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(nsXPCWrappedJS);
198 return NS_OK;
199 }
200
201 if (aIID.Equals(NS_GET_IID(nsCycleCollectionISupports))) {
202 *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this);
203 return NS_OK;
204 }
205
206 if (!IsValid()) {
207 return NS_ERROR_UNEXPECTED;
208 }
209
210 if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJSUnmarkGray))) {
211 *aInstancePtr = nullptr;
212
213 mJSObj.exposeToActiveJS();
214
215 // Just return some error value since one isn't supposed to use
216 // nsIXPConnectWrappedJSUnmarkGray objects for anything.
217 return NS_ERROR_FAILURE;
218 }
219
220 // Always check for this first so that our 'outer' can get this interface
221 // from us without recurring into a call to the outer's QI!
222 if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJS))) {
223 NS_ADDREF(this);
224 *aInstancePtr = (void*)static_cast<nsIXPConnectWrappedJS*>(this);
225 return NS_OK;
226 }
227
228 nsISupports* outer = GetAggregatedNativeObject();
229 if (outer) {
230 return outer->QueryInterface(aIID, aInstancePtr);
231 }
232
233 // else...
234
235 return DelegatedQueryInterface(aIID, aInstancePtr);
236 }
237
238 // For a description of nsXPCWrappedJS lifetime and reference counting, see
239 // the comment at the top of this file.
240
AddRef(void)241 MozExternalRefCountType nsXPCWrappedJS::AddRef(void) {
242 MOZ_RELEASE_ASSERT(NS_IsMainThread(),
243 "nsXPCWrappedJS::AddRef called off main thread");
244
245 MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
246 nsISupports* base =
247 NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this);
248 nsrefcnt cnt = mRefCnt.incr(base);
249 NS_LOG_ADDREF(this, cnt, "nsXPCWrappedJS", sizeof(*this));
250
251 if (2 == cnt && IsValid()) {
252 GetJSObject(); // Unmark gray JSObject.
253 XPCJSRuntime::Get()->AddWrappedJSRoot(this);
254 }
255
256 return cnt;
257 }
258
Release(void)259 MozExternalRefCountType nsXPCWrappedJS::Release(void) {
260 MOZ_RELEASE_ASSERT(NS_IsMainThread(),
261 "nsXPCWrappedJS::Release called off main thread");
262 MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
263 NS_ASSERT_OWNINGTHREAD(nsXPCWrappedJS);
264
265 bool shouldDelete = false;
266 nsISupports* base =
267 NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this);
268 nsrefcnt cnt = mRefCnt.decr(base, &shouldDelete);
269 NS_LOG_RELEASE(this, cnt, "nsXPCWrappedJS");
270
271 if (0 == cnt) {
272 if (MOZ_UNLIKELY(shouldDelete)) {
273 mRefCnt.stabilizeForDeletion();
274 DeleteCycleCollectable();
275 } else {
276 mRefCnt.incr(base);
277 Destroy();
278 mRefCnt.decr(base);
279 }
280 } else if (1 == cnt) {
281 if (IsValid()) {
282 RemoveFromRootSet();
283 }
284
285 // If we are not a root wrapper being used from a weak reference,
286 // then the extra ref is not needed and we can let ourselves be
287 // deleted.
288 if (!HasWeakReferences()) {
289 return Release();
290 }
291
292 MOZ_ASSERT(IsRootWrapper(),
293 "Only root wrappers should have weak references");
294 }
295 return cnt;
296 }
297
NS_IMETHODIMP_(void)298 NS_IMETHODIMP_(void)
299 nsXPCWrappedJS::DeleteCycleCollectable(void) { delete this; }
300
TraceJS(JSTracer * trc)301 void nsXPCWrappedJS::TraceJS(JSTracer* trc) {
302 MOZ_ASSERT(mRefCnt >= 2 && IsValid(), "must be strongly referenced");
303 JS::TraceEdge(trc, &mJSObj, "nsXPCWrappedJS::mJSObj");
304 }
305
306 NS_IMETHODIMP
GetWeakReference(nsIWeakReference ** aInstancePtr)307 nsXPCWrappedJS::GetWeakReference(nsIWeakReference** aInstancePtr) {
308 if (!IsRootWrapper()) {
309 return mRoot->GetWeakReference(aInstancePtr);
310 }
311
312 return nsSupportsWeakReference::GetWeakReference(aInstancePtr);
313 }
314
GetJSObject()315 JSObject* nsXPCWrappedJS::GetJSObject() { return mJSObj; }
316
GetJSObjectGlobal()317 JSObject* nsXPCWrappedJS::GetJSObjectGlobal() {
318 JSObject* obj = mJSObj;
319 if (js::IsCrossCompartmentWrapper(obj)) {
320 JS::Compartment* comp = JS::GetCompartment(obj);
321 return js::GetFirstGlobalInCompartment(comp);
322 }
323 return JS::GetNonCCWObjectGlobal(obj);
324 }
325
326 // static
GetNewOrUsed(JSContext * cx,JS::HandleObject jsObj,REFNSIID aIID,nsXPCWrappedJS ** wrapperResult)327 nsresult nsXPCWrappedJS::GetNewOrUsed(JSContext* cx, JS::HandleObject jsObj,
328 REFNSIID aIID,
329 nsXPCWrappedJS** wrapperResult) {
330 // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread.
331 MOZ_RELEASE_ASSERT(NS_IsMainThread(),
332 "nsXPCWrappedJS::GetNewOrUsed called off main thread");
333
334 MOZ_RELEASE_ASSERT(js::GetContextCompartment(cx) ==
335 JS::GetCompartment(jsObj));
336
337 const nsXPTInterfaceInfo* info = GetInterfaceInfo(aIID);
338 if (!info) {
339 return NS_ERROR_FAILURE;
340 }
341
342 JS::RootedObject rootJSObj(cx, GetRootJSObject(cx, jsObj));
343 if (!rootJSObj) {
344 return NS_ERROR_FAILURE;
345 }
346
347 xpc::CompartmentPrivate* rootComp = xpc::CompartmentPrivate::Get(rootJSObj);
348 MOZ_ASSERT(rootComp);
349
350 // Find any existing wrapper.
351 RefPtr<nsXPCWrappedJS> root = rootComp->GetWrappedJSMap()->Find(rootJSObj);
352 MOZ_ASSERT_IF(root, !nsXPConnect::GetRuntimeInstance()
353 ->GetMultiCompartmentWrappedJSMap()
354 ->Find(rootJSObj));
355 if (!root) {
356 root = nsXPConnect::GetRuntimeInstance()
357 ->GetMultiCompartmentWrappedJSMap()
358 ->Find(rootJSObj);
359 }
360
361 nsresult rv = NS_ERROR_FAILURE;
362 if (root) {
363 RefPtr<nsXPCWrappedJS> wrapper = root->FindOrFindInherited(aIID);
364 if (wrapper) {
365 wrapper.forget(wrapperResult);
366 return NS_OK;
367 }
368 } else if (rootJSObj != jsObj) {
369 // Make a new root wrapper, because there is no existing
370 // root wrapper, and the wrapper we are trying to make isn't
371 // a root.
372 const nsXPTInterfaceInfo* rootInfo =
373 GetInterfaceInfo(NS_GET_IID(nsISupports));
374 if (!rootInfo) {
375 return NS_ERROR_FAILURE;
376 }
377
378 root = new nsXPCWrappedJS(cx, rootJSObj, rootInfo, nullptr, &rv);
379 if (NS_FAILED(rv)) {
380 return rv;
381 }
382 }
383
384 RefPtr<nsXPCWrappedJS> wrapper =
385 new nsXPCWrappedJS(cx, jsObj, info, root, &rv);
386 if (NS_FAILED(rv)) {
387 return rv;
388 }
389 wrapper.forget(wrapperResult);
390 return NS_OK;
391 }
392
nsXPCWrappedJS(JSContext * cx,JSObject * aJSObj,const nsXPTInterfaceInfo * aInfo,nsXPCWrappedJS * root,nsresult * rv)393 nsXPCWrappedJS::nsXPCWrappedJS(JSContext* cx, JSObject* aJSObj,
394 const nsXPTInterfaceInfo* aInfo,
395 nsXPCWrappedJS* root, nsresult* rv)
396 : mJSObj(aJSObj), mInfo(aInfo), mRoot(root ? root : this), mNext(nullptr) {
397 *rv = InitStub(mInfo->IID());
398 // Continue even in the failure case, so that our refcounting/Destroy
399 // behavior works correctly.
400
401 // There is an extra AddRef to support weak references to wrappers
402 // that are subject to finalization. See the top of the file for more
403 // details.
404 NS_ADDREF_THIS();
405
406 if (IsRootWrapper()) {
407 MOZ_ASSERT(!IsMultiCompartment(), "mNext is always nullptr here");
408 if (!xpc::CompartmentPrivate::Get(mJSObj)->GetWrappedJSMap()->Add(cx,
409 this)) {
410 *rv = NS_ERROR_OUT_OF_MEMORY;
411 }
412 } else {
413 NS_ADDREF(mRoot);
414 mNext = mRoot->mNext;
415 mRoot->mNext = this;
416
417 // We always start wrappers in the per-compartment table. If adding
418 // this wrapper to the chain causes it to cross compartments, we need
419 // to migrate the chain to the global table on the XPCJSContext.
420 if (mRoot->IsMultiCompartment()) {
421 xpc::CompartmentPrivate::Get(mRoot->mJSObj)
422 ->GetWrappedJSMap()
423 ->Remove(mRoot);
424 auto destMap =
425 nsXPConnect::GetRuntimeInstance()->GetMultiCompartmentWrappedJSMap();
426 if (!destMap->Add(cx, mRoot)) {
427 *rv = NS_ERROR_OUT_OF_MEMORY;
428 }
429 }
430 }
431 }
432
~nsXPCWrappedJS()433 nsXPCWrappedJS::~nsXPCWrappedJS() { Destroy(); }
434
RemoveWrappedJS(nsXPCWrappedJS * wrapper)435 void XPCJSRuntime::RemoveWrappedJS(nsXPCWrappedJS* wrapper) {
436 AssertInvalidWrappedJSNotInTable(wrapper);
437 if (!wrapper->IsValid()) {
438 return;
439 }
440
441 // It is possible for the same JS XPCOM implementation object to be wrapped
442 // with a different interface in multiple JS::Compartments. In this case, the
443 // wrapper chain will contain references to multiple compartments. While we
444 // always store single-compartment chains in the per-compartment wrapped-js
445 // table, chains in the multi-compartment wrapped-js table may contain
446 // single-compartment chains, if they have ever contained a wrapper in a
447 // different compartment. Since removal requires a lookup anyway, we just do
448 // the remove on both tables unconditionally.
449 MOZ_ASSERT_IF(
450 wrapper->IsMultiCompartment(),
451 !xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor())
452 ->GetWrappedJSMap()
453 ->HasWrapper(wrapper));
454 GetMultiCompartmentWrappedJSMap()->Remove(wrapper);
455 xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor())
456 ->GetWrappedJSMap()
457 ->Remove(wrapper);
458 }
459
460 #ifdef DEBUG
NotHasWrapperAssertionCallback(JSContext * cx,void * data,JS::Compartment * comp)461 static JS::CompartmentIterResult NotHasWrapperAssertionCallback(
462 JSContext* cx, void* data, JS::Compartment* comp) {
463 auto wrapper = static_cast<nsXPCWrappedJS*>(data);
464 auto xpcComp = xpc::CompartmentPrivate::Get(comp);
465 MOZ_ASSERT_IF(xpcComp, !xpcComp->GetWrappedJSMap()->HasWrapper(wrapper));
466 return JS::CompartmentIterResult::KeepGoing;
467 }
468 #endif
469
AssertInvalidWrappedJSNotInTable(nsXPCWrappedJS * wrapper) const470 void XPCJSRuntime::AssertInvalidWrappedJSNotInTable(
471 nsXPCWrappedJS* wrapper) const {
472 #ifdef DEBUG
473 if (!wrapper->IsValid()) {
474 MOZ_ASSERT(!GetMultiCompartmentWrappedJSMap()->HasWrapper(wrapper));
475 if (!mGCIsRunning) {
476 JSContext* cx = XPCJSContext::Get()->Context();
477 JS_IterateCompartments(cx, wrapper, NotHasWrapperAssertionCallback);
478 }
479 }
480 #endif
481 }
482
Destroy()483 void nsXPCWrappedJS::Destroy() {
484 MOZ_ASSERT(1 == int32_t(mRefCnt), "should be stabilized for deletion");
485
486 if (IsRootWrapper()) {
487 nsXPConnect::GetRuntimeInstance()->RemoveWrappedJS(this);
488 }
489 Unlink();
490 }
491
Unlink()492 void nsXPCWrappedJS::Unlink() {
493 nsXPConnect::GetRuntimeInstance()->AssertInvalidWrappedJSNotInTable(this);
494
495 if (IsValid()) {
496 XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
497 if (rt) {
498 if (IsRootWrapper()) {
499 rt->RemoveWrappedJS(this);
500 }
501
502 if (mRefCnt > 1) {
503 RemoveFromRootSet();
504 }
505 }
506
507 mJSObj = nullptr;
508 }
509
510 if (IsRootWrapper()) {
511 ClearWeakReferences();
512 } else if (mRoot) {
513 // unlink this wrapper
514 nsXPCWrappedJS* cur = mRoot;
515 while (1) {
516 if (cur->mNext == this) {
517 cur->mNext = mNext;
518 break;
519 }
520 cur = cur->mNext;
521 MOZ_ASSERT(cur, "failed to find wrapper in its own chain");
522 }
523
524 // Note: unlinking this wrapper may have changed us from a multi-
525 // compartment wrapper chain to a single-compartment wrapper chain. We
526 // leave the wrapper in the multi-compartment table as it is likely to
527 // need to be multi-compartment again in the future and, moreover, we
528 // cannot get a JSContext here.
529
530 // let the root go
531 NS_RELEASE(mRoot);
532 }
533
534 if (mOuter) {
535 XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
536 if (rt->GCIsRunning()) {
537 DeferredFinalize(mOuter.forget().take());
538 } else {
539 mOuter = nullptr;
540 }
541 }
542 }
543
IsMultiCompartment() const544 bool nsXPCWrappedJS::IsMultiCompartment() const {
545 MOZ_ASSERT(IsRootWrapper());
546 JS::Compartment* compartment = Compartment();
547 nsXPCWrappedJS* next = mNext;
548 while (next) {
549 if (next->Compartment() != compartment) {
550 return true;
551 }
552 next = next->mNext;
553 }
554 return false;
555 }
556
Find(REFNSIID aIID)557 nsXPCWrappedJS* nsXPCWrappedJS::Find(REFNSIID aIID) {
558 if (aIID.Equals(NS_GET_IID(nsISupports))) {
559 return mRoot;
560 }
561
562 for (nsXPCWrappedJS* cur = mRoot; cur; cur = cur->mNext) {
563 if (aIID.Equals(cur->GetIID())) {
564 return cur;
565 }
566 }
567
568 return nullptr;
569 }
570
571 // check if asking for an interface that some wrapper in the chain inherits from
FindInherited(REFNSIID aIID)572 nsXPCWrappedJS* nsXPCWrappedJS::FindInherited(REFNSIID aIID) {
573 MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupports)), "bad call sequence");
574
575 for (nsXPCWrappedJS* cur = mRoot; cur; cur = cur->mNext) {
576 if (cur->mInfo->HasAncestor(aIID)) {
577 return cur;
578 }
579 }
580
581 return nullptr;
582 }
583
584 NS_IMETHODIMP
GetInterfaceIID(nsIID ** iid)585 nsXPCWrappedJS::GetInterfaceIID(nsIID** iid) {
586 MOZ_ASSERT(iid, "bad param");
587
588 *iid = GetIID().Clone();
589 return NS_OK;
590 }
591
SystemIsBeingShutDown()592 void nsXPCWrappedJS::SystemIsBeingShutDown() {
593 // XXX It turns out that it is better to leak here then to do any Releases
594 // and have them propagate into all sorts of mischief as the system is being
595 // shutdown. This was learned the hard way :(
596
597 // mJSObj == nullptr is used to indicate that the wrapper is no longer valid
598 // and that calls should fail without trying to use any of the
599 // xpconnect mechanisms. 'IsValid' is implemented by checking this pointer.
600
601 // Clear the contents of the pointer using unsafeGet() to avoid
602 // triggering post barriers in shutdown, as this will access the chunk
603 // containing mJSObj, which may have been freed at this point. This is safe
604 // if we are not currently running an incremental GC.
605 MOZ_ASSERT(!IsIncrementalGCInProgress(xpc_GetSafeJSContext()));
606 *mJSObj.unsafeGet() = nullptr;
607
608 // Notify other wrappers in the chain.
609 if (mNext) {
610 mNext->SystemIsBeingShutDown();
611 }
612 }
613
SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const614 size_t nsXPCWrappedJS::SizeOfIncludingThis(
615 mozilla::MallocSizeOf mallocSizeOf) const {
616 // mJSObject is a JS pointer, so don't measure the object. mInfo is
617 // not dynamically allocated. mRoot is not measured because it is
618 // either |this| or we have already measured it. mOuter is rare and
619 // probably not uniquely owned by this.
620 size_t n = mallocSizeOf(this);
621 n += nsAutoXPTCStub::SizeOfExcludingThis(mallocSizeOf);
622
623 // Wrappers form a linked list via the mNext field, so include them all
624 // in the measurement. Only root wrappers are stored in the map, so
625 // everything will be measured exactly once.
626 if (mNext) {
627 n += mNext->SizeOfIncludingThis(mallocSizeOf);
628 }
629
630 return n;
631 }
632
633 /***************************************************************************/
634
635 NS_IMETHODIMP
DebugDump(int16_t depth)636 nsXPCWrappedJS::DebugDump(int16_t depth) {
637 #ifdef DEBUG
638 XPC_LOG_ALWAYS(
639 ("nsXPCWrappedJS @ %p with mRefCnt = %" PRIuPTR, this, mRefCnt.get()));
640 XPC_LOG_INDENT();
641
642 XPC_LOG_ALWAYS(("%s wrapper around JSObject @ %p",
643 IsRootWrapper() ? "ROOT" : "non-root", mJSObj.get()));
644 const char* name = mInfo->Name();
645 XPC_LOG_ALWAYS(("interface name is %s", name));
646 char* iid = mInfo->IID().ToString();
647 XPC_LOG_ALWAYS(("IID number is %s", iid ? iid : "invalid"));
648 if (iid) {
649 free(iid);
650 }
651 XPC_LOG_ALWAYS(("nsXPTInterfaceInfo @ %p", mInfo));
652
653 if (!IsRootWrapper()) {
654 XPC_LOG_OUTDENT();
655 }
656 if (mNext) {
657 if (IsRootWrapper()) {
658 XPC_LOG_ALWAYS(("Additional wrappers for this object..."));
659 XPC_LOG_INDENT();
660 }
661 mNext->DebugDump(depth);
662 if (IsRootWrapper()) {
663 XPC_LOG_OUTDENT();
664 }
665 }
666 if (IsRootWrapper()) {
667 XPC_LOG_OUTDENT();
668 }
669 #endif
670 return NS_OK;
671 }
672