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