1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set ts=8 sts=4 et sw=4 tw=99: */
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 used to manage the wrapped native objects within a JS scope. */
8 
9 #include "xpcprivate.h"
10 #include "XPCWrapper.h"
11 #include "nsContentUtils.h"
12 #include "nsCycleCollectionNoteRootCallback.h"
13 #include "ExpandedPrincipal.h"
14 #include "mozilla/MemoryReporting.h"
15 #include "mozilla/Preferences.h"
16 #include "nsIXULRuntime.h"
17 #include "mozJSComponentLoader.h"
18 
19 #include "mozilla/dom/BindingUtils.h"
20 
21 using namespace mozilla;
22 using namespace xpc;
23 using namespace JS;
24 
25 /***************************************************************************/
26 
27 XPCWrappedNativeScope* XPCWrappedNativeScope::gScopes = nullptr;
28 XPCWrappedNativeScope* XPCWrappedNativeScope::gDyingScopes = nullptr;
29 bool XPCWrappedNativeScope::gShutdownObserverInitialized = false;
30 XPCWrappedNativeScope::AddonSet* XPCWrappedNativeScope::gAllowCPOWAddonSet =
31     nullptr;
32 
NS_IMPL_ISUPPORTS(XPCWrappedNativeScope::ClearInterpositionsObserver,nsIObserver)33 NS_IMPL_ISUPPORTS(XPCWrappedNativeScope::ClearInterpositionsObserver,
34                   nsIObserver)
35 
36 NS_IMETHODIMP
37 XPCWrappedNativeScope::ClearInterpositionsObserver::Observe(
38     nsISupports* subject, const char* topic, const char16_t* data) {
39   MOZ_ASSERT(strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
40 
41   if (gAllowCPOWAddonSet) {
42     delete gAllowCPOWAddonSet;
43     gAllowCPOWAddonSet = nullptr;
44   }
45 
46   nsContentUtils::UnregisterShutdownObserver(this);
47   return NS_OK;
48 }
49 
RemoteXULForbidsXBLScope(nsIPrincipal * aPrincipal,HandleObject aGlobal)50 static bool RemoteXULForbidsXBLScope(nsIPrincipal* aPrincipal,
51                                      HandleObject aGlobal) {
52   MOZ_ASSERT(aPrincipal);
53 
54   // Certain singleton sandoxes are created very early in startup - too early
55   // to call into AllowXULXBLForPrincipal. We never create XBL scopes for
56   // sandboxes anway, and certainly not for these singleton scopes. So we just
57   // short-circuit here.
58   if (IsSandbox(aGlobal)) return false;
59 
60   // AllowXULXBLForPrincipal will return true for system principal, but we
61   // don't want that here.
62   MOZ_ASSERT(nsContentUtils::IsInitialized());
63   if (nsContentUtils::IsSystemPrincipal(aPrincipal)) return false;
64 
65   // If this domain isn't whitelisted, we're done.
66   if (!nsContentUtils::AllowXULXBLForPrincipal(aPrincipal)) return false;
67 
68   // Check the pref to determine how we should behave.
69   return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false);
70 }
71 
XPCWrappedNativeScope(JSContext * cx,JS::HandleObject aGlobal)72 XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx,
73                                              JS::HandleObject aGlobal)
74     : mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)),
75       mWrappedNativeProtoMap(
76           ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)),
77       mComponents(nullptr),
78       mNext(nullptr),
79       mGlobalJSObject(aGlobal) {
80   // add ourselves to the scopes list
81   {
82     MOZ_ASSERT(aGlobal);
83     DebugOnly<const js::Class*> clasp = js::GetObjectClass(aGlobal);
84     MOZ_ASSERT(clasp->flags &
85                    (JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_HAS_PRIVATE) ||
86                mozilla::dom::IsDOMClass(clasp));
87 #ifdef DEBUG
88     for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext)
89       MOZ_ASSERT(aGlobal != cur->GetGlobalJSObjectPreserveColor(),
90                  "dup object");
91 #endif
92 
93     mNext = gScopes;
94     gScopes = this;
95   }
96 
97   MOZ_COUNT_CTOR(XPCWrappedNativeScope);
98 
99   // Create the compartment private.
100   JSCompartment* c = js::GetObjectCompartment(aGlobal);
101   MOZ_ASSERT(!JS_GetCompartmentPrivate(c));
102   CompartmentPrivate* priv = new CompartmentPrivate(c);
103   JS_SetCompartmentPrivate(c, priv);
104 
105   // Attach ourselves to the realm private.
106   Realm* realm = JS::GetObjectRealmOrNull(aGlobal);
107   RealmPrivate* realmPriv = new RealmPrivate(realm);
108   realmPriv->scope = this;
109   JS::SetRealmPrivate(realm, realmPriv);
110 
111   // Determine whether we would allow an XBL scope in this situation.
112   // In addition to being pref-controlled, we also disable XBL scopes for
113   // remote XUL domains, _except_ if we have an additional pref override set.
114   nsIPrincipal* principal = GetPrincipal();
115   mAllowContentXBLScope = !RemoteXULForbidsXBLScope(principal, aGlobal);
116 
117   // Determine whether to use an XBL scope.
118   mUseContentXBLScope = mAllowContentXBLScope;
119   if (mUseContentXBLScope) {
120     const js::Class* clasp = js::GetObjectClass(mGlobalJSObject);
121     mUseContentXBLScope = !strcmp(clasp->name, "Window");
122   }
123   if (mUseContentXBLScope) {
124     mUseContentXBLScope =
125         principal && !nsContentUtils::IsSystemPrincipal(principal);
126   }
127 
128   if (JSAddonId* addonId = JS::AddonIdOfObject(aGlobal)) {
129     // We forbid CPOWs unless they're specifically allowed.
130     priv->allowCPOWs =
131         gAllowCPOWAddonSet ? gAllowCPOWAddonSet->has(addonId) : false;
132     MOZ_ASSERT(!mozJSComponentLoader::Get()->IsLoaderGlobal(aGlobal),
133                "Don't load addons into the shared JSM global");
134   }
135 }
136 
137 // static
IsDyingScope(XPCWrappedNativeScope * scope)138 bool XPCWrappedNativeScope::IsDyingScope(XPCWrappedNativeScope* scope) {
139   for (XPCWrappedNativeScope* cur = gDyingScopes; cur; cur = cur->mNext) {
140     if (scope == cur) return true;
141   }
142   return false;
143 }
144 
GetComponentsJSObject(JS::MutableHandleObject obj)145 bool XPCWrappedNativeScope::GetComponentsJSObject(JS::MutableHandleObject obj) {
146   AutoJSContext cx;
147   if (!mComponents) {
148     nsIPrincipal* p = GetPrincipal();
149     bool system = nsXPConnect::SecurityManager()->IsSystemPrincipal(p);
150     mComponents =
151         system ? new nsXPCComponents(this) : new nsXPCComponentsBase(this);
152   }
153 
154   RootedValue val(cx);
155   xpcObjectHelper helper(mComponents);
156   bool ok = XPCConvert::NativeInterface2JSObject(&val, helper, nullptr, false,
157                                                  nullptr);
158   if (NS_WARN_IF(!ok)) return false;
159 
160   if (NS_WARN_IF(!val.isObject())) return false;
161 
162   // The call to wrap() here is necessary even though the object is same-
163   // compartment, because it applies our security wrapper.
164   obj.set(&val.toObject());
165   if (NS_WARN_IF(!JS_WrapObject(cx, obj))) return false;
166   return true;
167 }
168 
ForcePrivilegedComponents()169 void XPCWrappedNativeScope::ForcePrivilegedComponents() {
170   nsCOMPtr<nsIXPCComponents> c = do_QueryInterface(mComponents);
171   if (!c) mComponents = new nsXPCComponents(this);
172 }
173 
DefineSubcomponentProperty(JSContext * aCx,HandleObject aGlobal,nsISupports * aSubcomponent,const nsID * aIID,unsigned int aStringIndex)174 static bool DefineSubcomponentProperty(JSContext* aCx, HandleObject aGlobal,
175                                        nsISupports* aSubcomponent,
176                                        const nsID* aIID,
177                                        unsigned int aStringIndex) {
178   RootedValue subcompVal(aCx);
179   xpcObjectHelper helper(aSubcomponent);
180   if (!XPCConvert::NativeInterface2JSObject(&subcompVal, helper, aIID, false,
181                                             nullptr))
182     return false;
183   if (NS_WARN_IF(!subcompVal.isObject())) return false;
184   RootedId id(aCx, XPCJSContext::Get()->GetStringID(aStringIndex));
185   return JS_DefinePropertyById(aCx, aGlobal, id, subcompVal, 0);
186 }
187 
AttachComponentsObject(JSContext * aCx)188 bool XPCWrappedNativeScope::AttachComponentsObject(JSContext* aCx) {
189   RootedObject components(aCx);
190   if (!GetComponentsJSObject(&components)) return false;
191 
192   RootedObject global(aCx, GetGlobalJSObject());
193   MOZ_ASSERT(js::IsObjectInContextCompartment(global, aCx));
194 
195   // The global Components property is non-configurable if it's a full
196   // nsXPCComponents object. That way, if it's an nsXPCComponentsBase,
197   // enableUniversalXPConnect can upgrade it later.
198   unsigned attrs = JSPROP_READONLY | JSPROP_RESOLVING;
199   nsCOMPtr<nsIXPCComponents> c = do_QueryInterface(mComponents);
200   if (c) attrs |= JSPROP_PERMANENT;
201 
202   RootedId id(aCx,
203               XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS));
204   if (!JS_DefinePropertyById(aCx, global, id, components, attrs)) return false;
205 
206 // _iid can be nullptr if the object implements classinfo.
207 #define DEFINE_SUBCOMPONENT_PROPERTY(_comp, _type, _iid, _id)                 \
208   nsCOMPtr<nsIXPCComponents_##_type> obj##_type;                              \
209   if (NS_FAILED(_comp->Get##_type(getter_AddRefs(obj##_type)))) return false; \
210   if (!DefineSubcomponentProperty(aCx, global, obj##_type, _iid,              \
211                                   XPCJSContext::IDX_##_id))                   \
212     return false;
213 
214   DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Interfaces, nullptr, CI)
215   DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Results, nullptr, CR)
216 
217   if (!c) return true;
218 
219   DEFINE_SUBCOMPONENT_PROPERTY(c, Classes, nullptr, CC)
220   DEFINE_SUBCOMPONENT_PROPERTY(c, Utils, &NS_GET_IID(nsIXPCComponents_Utils),
221                                CU)
222 
223 #undef DEFINE_SUBCOMPONENT_PROPERTY
224 
225   return true;
226 }
227 
CompartmentPerAddon()228 static bool CompartmentPerAddon() {
229   static bool initialized = false;
230   static bool pref = false;
231 
232   if (!initialized) {
233     pref = Preferences::GetBool("dom.compartment_per_addon", false) ||
234            BrowserTabsRemoteAutostart();
235     initialized = true;
236   }
237 
238   return pref;
239 }
240 
EnsureContentXBLScope(JSContext * cx)241 JSObject* XPCWrappedNativeScope::EnsureContentXBLScope(JSContext* cx) {
242   JS::RootedObject global(cx, GetGlobalJSObject());
243   MOZ_ASSERT(js::IsObjectInContextCompartment(global, cx));
244   MOZ_ASSERT(!IsContentXBLScope());
245   MOZ_ASSERT(strcmp(js::GetObjectClass(global)->name,
246                     "nsXBLPrototypeScript compilation scope"));
247 
248   // If we already have a special XBL scope object, we know what to use.
249   if (mContentXBLScope) return mContentXBLScope;
250 
251   // If this scope doesn't need an XBL scope, just return the global.
252   if (!mUseContentXBLScope) return global;
253 
254   // Set up the sandbox options. Note that we use the DOM global as the
255   // sandboxPrototype so that the XBL scope can access all the DOM objects
256   // it's accustomed to accessing.
257   //
258   // In general wantXrays shouldn't matter much here, but there are weird
259   // cases when adopting bound content between same-origin globals where a
260   // <destructor> in one content XBL scope sees anonymous content in another
261   // content XBL scope. When that happens, we hit LookupBindingMember for an
262   // anonymous element that lives in a content XBL scope, which isn't a tested
263   // or audited codepath. So let's avoid hitting that case by opting out of
264   // same-origin Xrays.
265   SandboxOptions options;
266   options.wantXrays = false;
267   options.wantComponents = true;
268   options.proto = global;
269   options.sameZoneAs = global;
270   options.isContentXBLScope = true;
271 
272   // Use an ExpandedPrincipal to create asymmetric security.
273   nsIPrincipal* principal = GetPrincipal();
274   MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal));
275   nsTArray<nsCOMPtr<nsIPrincipal>> principalAsArray(1);
276   principalAsArray.AppendElement(principal);
277   RefPtr<ExpandedPrincipal> ep = ExpandedPrincipal::Create(
278       principalAsArray, principal->OriginAttributesRef());
279 
280   // Create the sandbox.
281   RootedValue v(cx);
282   nsresult rv = CreateSandboxObject(
283       cx, &v, static_cast<nsIExpandedPrincipal*>(ep), options);
284   NS_ENSURE_SUCCESS(rv, nullptr);
285   mContentXBLScope = &v.toObject();
286 
287   MOZ_ASSERT(xpc::IsInContentXBLScope(js::UncheckedUnwrap(mContentXBLScope)));
288 
289   // Good to go!
290   return mContentXBLScope;
291 }
292 
AllowContentXBLScope()293 bool XPCWrappedNativeScope::AllowContentXBLScope() {
294   // We only disallow XBL scopes in remote XUL situations.
295   MOZ_ASSERT_IF(!mAllowContentXBLScope,
296                 nsContentUtils::AllowXULXBLForPrincipal(GetPrincipal()));
297   return mAllowContentXBLScope;
298 }
299 
300 namespace xpc {
GetXBLScope(JSContext * cx,JSObject * contentScopeArg)301 JSObject* GetXBLScope(JSContext* cx, JSObject* contentScopeArg) {
302   MOZ_ASSERT(!IsInAddonScope(contentScopeArg));
303 
304   JS::RootedObject contentScope(cx, contentScopeArg);
305   JSCompartment* addonComp = js::GetObjectCompartment(contentScope);
306   JS::Rooted<JS::Realm*> addonRealm(cx, JS::GetRealmForCompartment(addonComp));
307   JSAutoCompartment ac(cx, contentScope);
308   JSObject* scope =
309       RealmPrivate::Get(addonRealm)->scope->EnsureContentXBLScope(cx);
310   NS_ENSURE_TRUE(scope, nullptr);  // See bug 858642.
311   scope = js::UncheckedUnwrap(scope);
312   JS::ExposeObjectToActiveJS(scope);
313   return scope;
314 }
315 
GetScopeForXBLExecution(JSContext * cx,HandleObject contentScope,JSAddonId * addonId)316 JSObject* GetScopeForXBLExecution(JSContext* cx, HandleObject contentScope,
317                                   JSAddonId* addonId) {
318   MOZ_RELEASE_ASSERT(!IsInAddonScope(contentScope));
319 
320   RootedObject global(cx, js::GetGlobalForObjectCrossCompartment(contentScope));
321   if (IsInContentXBLScope(contentScope)) return global;
322 
323   JSAutoCompartment ac(cx, contentScope);
324   XPCWrappedNativeScope* nativeScope = RealmPrivate::Get(contentScope)->scope;
325   bool isSystem =
326       nsContentUtils::IsSystemPrincipal(nativeScope->GetPrincipal());
327 
328   RootedObject scope(cx);
329   if (nativeScope->UseContentXBLScope())
330     scope = nativeScope->EnsureContentXBLScope(cx);
331   else if (addonId && CompartmentPerAddon() && isSystem)
332     scope = nativeScope->EnsureAddonScope(cx, addonId);
333   else
334     scope = global;
335 
336   NS_ENSURE_TRUE(scope, nullptr);  // See bug 858642.
337   scope = js::UncheckedUnwrap(scope);
338   JS::ExposeObjectToActiveJS(scope);
339   return scope;
340 }
341 
AllowContentXBLScope(JS::Realm * realm)342 bool AllowContentXBLScope(JS::Realm* realm) {
343   XPCWrappedNativeScope* scope = RealmPrivate::Get(realm)->scope;
344   return scope && scope->AllowContentXBLScope();
345 }
346 
UseContentXBLScope(JS::Realm * realm)347 bool UseContentXBLScope(JS::Realm* realm) {
348   XPCWrappedNativeScope* scope = RealmPrivate::Get(realm)->scope;
349   return scope && scope->UseContentXBLScope();
350 }
351 
ClearContentXBLScope(JSObject * global)352 void ClearContentXBLScope(JSObject* global) {
353   RealmPrivate::Get(global)->scope->ClearContentXBLScope();
354 }
355 
356 } /* namespace xpc */
357 
EnsureAddonScope(JSContext * cx,JSAddonId * addonId)358 JSObject* XPCWrappedNativeScope::EnsureAddonScope(JSContext* cx,
359                                                   JSAddonId* addonId) {
360   JS::RootedObject global(cx, GetGlobalJSObject());
361   MOZ_ASSERT(js::IsObjectInContextCompartment(global, cx));
362   MOZ_ASSERT(!IsContentXBLScope());
363   MOZ_ASSERT(!IsAddonScope());
364   MOZ_ASSERT(addonId);
365   MOZ_ASSERT(nsContentUtils::IsSystemPrincipal(GetPrincipal()));
366 
367   // In bug 1092156, we found that add-on scopes don't work correctly when the
368   // window navigates. The add-on global's prototype is an outer window, so,
369   // after the navigation, looking up window properties in the add-on scope
370   // will fail. However, in most cases where the window can be navigated, the
371   // entire window is part of the add-on. To solve the problem, we avoid
372   // returning an add-on scope for a window that is already tagged with the
373   // add-on ID.
374   if (AddonIdOfObject(global) == addonId) return global;
375 
376   // If we already have an addon scope object, we know what to use.
377   for (size_t i = 0; i < mAddonScopes.Length(); i++) {
378     if (JS::AddonIdOfObject(js::UncheckedUnwrap(mAddonScopes[i])) == addonId)
379       return mAddonScopes[i];
380   }
381 
382   SandboxOptions options;
383   options.wantComponents = true;
384   options.proto = global;
385   options.sameZoneAs = global;
386   options.addonId = JS::StringOfAddonId(addonId);
387   options.writeToGlobalPrototype = true;
388 
389   RootedValue v(cx);
390   nsresult rv = CreateSandboxObject(cx, &v, GetPrincipal(), options);
391   NS_ENSURE_SUCCESS(rv, nullptr);
392   mAddonScopes.AppendElement(&v.toObject());
393 
394   CompartmentPrivate::Get(js::UncheckedUnwrap(&v.toObject()))
395       ->isAddonCompartment = true;
396   return &v.toObject();
397 }
398 
GetAddonScope(JSContext * cx,JS::HandleObject contentScope,JSAddonId * addonId)399 JSObject* xpc::GetAddonScope(JSContext* cx, JS::HandleObject contentScope,
400                              JSAddonId* addonId) {
401   MOZ_RELEASE_ASSERT(!IsInAddonScope(contentScope));
402 
403   if (!addonId || !CompartmentPerAddon()) {
404     return js::GetGlobalForObjectCrossCompartment(contentScope);
405   }
406 
407   JSAutoCompartment ac(cx, contentScope);
408   XPCWrappedNativeScope* nativeScope = RealmPrivate::Get(contentScope)->scope;
409   if (nativeScope->GetPrincipal() != nsXPConnect::SystemPrincipal()) {
410     // This can happen if, for example, Jetpack loads an unprivileged HTML
411     // page from the add-on. It's not clear what to do there, so we just use
412     // the normal global.
413     return js::GetGlobalForObjectCrossCompartment(contentScope);
414   }
415   JSObject* scope = nativeScope->EnsureAddonScope(cx, addonId);
416   NS_ENSURE_TRUE(scope, nullptr);
417 
418   scope = js::UncheckedUnwrap(scope);
419   JS::ExposeObjectToActiveJS(scope);
420   return scope;
421 }
422 
~XPCWrappedNativeScope()423 XPCWrappedNativeScope::~XPCWrappedNativeScope() {
424   MOZ_COUNT_DTOR(XPCWrappedNativeScope);
425 
426   // We can do additional cleanup assertions here...
427 
428   MOZ_ASSERT(0 == mWrappedNativeMap->Count(), "scope has non-empty map");
429   delete mWrappedNativeMap;
430 
431   MOZ_ASSERT(0 == mWrappedNativeProtoMap->Count(), "scope has non-empty map");
432   delete mWrappedNativeProtoMap;
433 
434   // This should not be necessary, since the Components object should die
435   // with the scope but just in case.
436   if (mComponents) mComponents->mScope = nullptr;
437 
438   // XXX we should assert that we are dead or that xpconnect has shutdown
439   // XXX might not want to do this at xpconnect shutdown time???
440   mComponents = nullptr;
441 
442   if (mXrayExpandos.initialized()) mXrayExpandos.destroy();
443 
444   JSContext* cx = dom::danger::GetJSContext();
445   mGlobalJSObject.finalize(cx);
446 }
447 
448 // static
TraceWrappedNativesInAllScopes(JSTracer * trc)449 void XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(JSTracer* trc) {
450   // Do JS::TraceEdge for all wrapped natives with external references, as
451   // well as any DOM expando objects.
452   for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
453     for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
454       auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
455       XPCWrappedNative* wrapper = entry->value;
456       if (wrapper->HasExternalReference() && !wrapper->IsWrapperExpired())
457         wrapper->TraceSelf(trc);
458     }
459   }
460 }
461 
462 // static
SuspectAllWrappers(nsCycleCollectionNoteRootCallback & cb)463 void XPCWrappedNativeScope::SuspectAllWrappers(
464     nsCycleCollectionNoteRootCallback& cb) {
465   for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
466     for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
467       static_cast<Native2WrappedNativeMap::Entry*>(i.Get())->value->Suspect(cb);
468     }
469   }
470 }
471 
472 // static
UpdateWeakPointersInAllScopesAfterGC()473 void XPCWrappedNativeScope::UpdateWeakPointersInAllScopesAfterGC() {
474   // If this is called from the finalization callback in JSGC_MARK_END then
475   // JSGC_FINALIZE_END must always follow it calling
476   // FinishedFinalizationPhaseOfGC and clearing gDyingScopes in
477   // KillDyingScopes.
478   MOZ_ASSERT(!gDyingScopes, "JSGC_MARK_END without JSGC_FINALIZE_END");
479 
480   XPCWrappedNativeScope** scopep = &gScopes;
481   while (*scopep) {
482     XPCWrappedNativeScope* cur = *scopep;
483     cur->UpdateWeakPointersAfterGC();
484     if (cur->mGlobalJSObject) {
485       scopep = &cur->mNext;
486     } else {
487       // The scope's global is dead so move it to the dying scopes list.
488       *scopep = cur->mNext;
489       cur->mNext = gDyingScopes;
490       gDyingScopes = cur;
491     }
492   }
493 }
494 
AssertSameCompartment(DebugOnly<JSCompartment * > & comp,JSObject * obj)495 static inline void AssertSameCompartment(DebugOnly<JSCompartment*>& comp,
496                                          JSObject* obj) {
497   MOZ_ASSERT_IF(obj, js::GetObjectCompartment(obj) == comp);
498 }
499 
AssertSameCompartment(DebugOnly<JSCompartment * > & comp,const JS::ObjectPtr & obj)500 static inline void AssertSameCompartment(DebugOnly<JSCompartment*>& comp,
501                                          const JS::ObjectPtr& obj) {
502 #ifdef DEBUG
503   AssertSameCompartment(comp, obj.unbarrieredGet());
504 #endif
505 }
506 
UpdateWeakPointersAfterGC()507 void XPCWrappedNativeScope::UpdateWeakPointersAfterGC() {
508   // Sweep waivers.
509   if (mWaiverWrapperMap) mWaiverWrapperMap->Sweep();
510 
511   if (!js::IsObjectZoneSweepingOrCompacting(mGlobalJSObject.unbarrieredGet()))
512     return;
513 
514   // Update our pointer to the global object in case it was moved or
515   // finalized.
516   mGlobalJSObject.updateWeakPointerAfterGC();
517   if (!mGlobalJSObject) {
518     JSContext* cx = dom::danger::GetJSContext();
519     mContentXBLScope.finalize(cx);
520     for (size_t i = 0; i < mAddonScopes.Length(); i++)
521       mAddonScopes[i].finalize(cx);
522     GetWrappedNativeMap()->Clear();
523     mWrappedNativeProtoMap->Clear();
524     return;
525   }
526 
527   DebugOnly<JSCompartment*> comp =
528       js::GetObjectCompartment(mGlobalJSObject.unbarrieredGet());
529 
530 #ifdef DEBUG
531   // These are traced, so no updates are necessary.
532   if (mContentXBLScope) {
533     JSObject* prev = mContentXBLScope.unbarrieredGet();
534     mContentXBLScope.updateWeakPointerAfterGC();
535     MOZ_ASSERT(prev == mContentXBLScope.unbarrieredGet());
536     AssertSameCompartment(comp, mContentXBLScope);
537   }
538   for (size_t i = 0; i < mAddonScopes.Length(); i++) {
539     JSObject* prev = mAddonScopes[i].unbarrieredGet();
540     mAddonScopes[i].updateWeakPointerAfterGC();
541     MOZ_ASSERT(prev == mAddonScopes[i].unbarrieredGet());
542     AssertSameCompartment(comp, mAddonScopes[i]);
543   }
544 #endif
545 
546   // Sweep mWrappedNativeMap for dying flat JS objects. Moving has already
547   // been handled by XPCWrappedNative::FlatJSObjectMoved.
548   for (auto iter = GetWrappedNativeMap()->Iter(); !iter.Done(); iter.Next()) {
549     auto entry = static_cast<Native2WrappedNativeMap::Entry*>(iter.Get());
550     XPCWrappedNative* wrapper = entry->value;
551     JSObject* obj = wrapper->GetFlatJSObjectPreserveColor();
552     JS_UpdateWeakPointerAfterGCUnbarriered(&obj);
553     MOZ_ASSERT(!obj || obj == wrapper->GetFlatJSObjectPreserveColor());
554     AssertSameCompartment(comp, obj);
555     if (!obj) iter.Remove();
556   }
557 
558   // Sweep mWrappedNativeProtoMap for dying prototype JSObjects. Moving has
559   // already been handled by XPCWrappedNativeProto::JSProtoObjectMoved.
560   for (auto i = mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
561     auto entry = static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
562     JSObject* obj = entry->value->GetJSProtoObjectPreserveColor();
563     JS_UpdateWeakPointerAfterGCUnbarriered(&obj);
564     AssertSameCompartment(comp, obj);
565     MOZ_ASSERT(!obj || obj == entry->value->GetJSProtoObjectPreserveColor());
566     if (!obj) i.Remove();
567   }
568 }
569 
570 // static
SweepAllWrappedNativeTearOffs()571 void XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs() {
572   for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
573     for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
574       auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
575       entry->value->SweepTearOffs();
576     }
577   }
578 }
579 
580 // static
KillDyingScopes()581 void XPCWrappedNativeScope::KillDyingScopes() {
582   XPCWrappedNativeScope* cur = gDyingScopes;
583   while (cur) {
584     XPCWrappedNativeScope* next = cur->mNext;
585     if (cur->mGlobalJSObject)
586       RealmPrivate::Get(cur->mGlobalJSObject)->scope = nullptr;
587     delete cur;
588     cur = next;
589   }
590   gDyingScopes = nullptr;
591 }
592 
593 // static
SystemIsBeingShutDown()594 void XPCWrappedNativeScope::SystemIsBeingShutDown() {
595   int liveScopeCount = 0;
596 
597   XPCWrappedNativeScope* cur;
598 
599   // First move all the scopes to the dying list.
600 
601   cur = gScopes;
602   while (cur) {
603     XPCWrappedNativeScope* next = cur->mNext;
604     cur->mNext = gDyingScopes;
605     gDyingScopes = cur;
606     cur = next;
607     liveScopeCount++;
608   }
609   gScopes = nullptr;
610 
611   // We're forcibly killing scopes, rather than allowing them to go away
612   // when they're ready. As such, we need to do some cleanup before they
613   // can safely be destroyed.
614 
615   for (cur = gDyingScopes; cur; cur = cur->mNext) {
616     // Give the Components object a chance to try to clean up.
617     if (cur->mComponents) cur->mComponents->SystemIsBeingShutDown();
618 
619     // Walk the protos first. Wrapper shutdown can leave dangling
620     // proto pointers in the proto map.
621     for (auto i = cur->mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
622       auto entry =
623           static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
624       entry->value->SystemIsBeingShutDown();
625       i.Remove();
626     }
627     for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
628       auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
629       XPCWrappedNative* wrapper = entry->value;
630       if (wrapper->IsValid()) {
631         wrapper->SystemIsBeingShutDown();
632       }
633       i.Remove();
634     }
635   }
636 
637   // Now it is safe to kill all the scopes.
638   KillDyingScopes();
639 }
640 
641 /***************************************************************************/
642 
GetExpandoChain(HandleObject target)643 JSObject* XPCWrappedNativeScope::GetExpandoChain(HandleObject target) {
644   MOZ_ASSERT(ObjectScope(target) == this);
645   if (!mXrayExpandos.initialized()) return nullptr;
646   return mXrayExpandos.lookup(target);
647 }
648 
DetachExpandoChain(HandleObject target)649 JSObject* XPCWrappedNativeScope::DetachExpandoChain(HandleObject target) {
650   MOZ_ASSERT(ObjectScope(target) == this);
651   if (!mXrayExpandos.initialized()) return nullptr;
652   return mXrayExpandos.removeValue(target);
653 }
654 
SetExpandoChain(JSContext * cx,HandleObject target,HandleObject chain)655 bool XPCWrappedNativeScope::SetExpandoChain(JSContext* cx, HandleObject target,
656                                             HandleObject chain) {
657   MOZ_ASSERT(ObjectScope(target) == this);
658   MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx));
659   MOZ_ASSERT_IF(chain, ObjectScope(chain) == this);
660   if (!mXrayExpandos.initialized() && !mXrayExpandos.init(cx)) return false;
661   return mXrayExpandos.put(cx, target, chain);
662 }
663 
AllowCPOWsInAddon(JSContext * cx,JSAddonId * addonId,bool allow)664 /* static */ bool XPCWrappedNativeScope::AllowCPOWsInAddon(JSContext* cx,
665                                                            JSAddonId* addonId,
666                                                            bool allow) {
667   if (!gAllowCPOWAddonSet) {
668     gAllowCPOWAddonSet = new AddonSet();
669     bool ok = gAllowCPOWAddonSet->init();
670     NS_ENSURE_TRUE(ok, false);
671 
672     if (!gShutdownObserverInitialized) {
673       gShutdownObserverInitialized = true;
674       nsContentUtils::RegisterShutdownObserver(
675           new ClearInterpositionsObserver());
676     }
677   }
678   if (allow) {
679     bool ok = gAllowCPOWAddonSet->put(addonId);
680     NS_ENSURE_TRUE(ok, false);
681   } else {
682     gAllowCPOWAddonSet->remove(addonId);
683   }
684   return true;
685 }
686 
687 /***************************************************************************/
688 
689 // static
DebugDumpAllScopes(int16_t depth)690 void XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth) {
691 #ifdef DEBUG
692   depth--;
693 
694   // get scope count.
695   int count = 0;
696   XPCWrappedNativeScope* cur;
697   for (cur = gScopes; cur; cur = cur->mNext) count++;
698 
699   XPC_LOG_ALWAYS(("chain of %d XPCWrappedNativeScope(s)", count));
700   XPC_LOG_INDENT();
701   XPC_LOG_ALWAYS(("gDyingScopes @ %p", gDyingScopes));
702   if (depth)
703     for (cur = gScopes; cur; cur = cur->mNext) cur->DebugDump(depth);
704   XPC_LOG_OUTDENT();
705 #endif
706 }
707 
DebugDump(int16_t depth)708 void XPCWrappedNativeScope::DebugDump(int16_t depth) {
709 #ifdef DEBUG
710   depth--;
711   XPC_LOG_ALWAYS(("XPCWrappedNativeScope @ %p", this));
712   XPC_LOG_INDENT();
713   XPC_LOG_ALWAYS(("mNext @ %p", mNext));
714   XPC_LOG_ALWAYS(("mComponents @ %p", mComponents.get()));
715   XPC_LOG_ALWAYS(("mGlobalJSObject @ %p", mGlobalJSObject.get()));
716 
717   XPC_LOG_ALWAYS(("mWrappedNativeMap @ %p with %d wrappers(s)",
718                   mWrappedNativeMap, mWrappedNativeMap->Count()));
719   // iterate contexts...
720   if (depth && mWrappedNativeMap->Count()) {
721     XPC_LOG_INDENT();
722     for (auto i = mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
723       auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
724       entry->value->DebugDump(depth);
725     }
726     XPC_LOG_OUTDENT();
727   }
728 
729   XPC_LOG_ALWAYS(("mWrappedNativeProtoMap @ %p with %d protos(s)",
730                   mWrappedNativeProtoMap, mWrappedNativeProtoMap->Count()));
731   // iterate contexts...
732   if (depth && mWrappedNativeProtoMap->Count()) {
733     XPC_LOG_INDENT();
734     for (auto i = mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
735       auto entry =
736           static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
737       entry->value->DebugDump(depth);
738     }
739     XPC_LOG_OUTDENT();
740   }
741   XPC_LOG_OUTDENT();
742 #endif
743 }
744 
AddSizeOfAllScopesIncludingThis(ScopeSizeInfo * scopeSizeInfo)745 void XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(
746     ScopeSizeInfo* scopeSizeInfo) {
747   for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext)
748     cur->AddSizeOfIncludingThis(scopeSizeInfo);
749 }
750 
AddSizeOfIncludingThis(ScopeSizeInfo * scopeSizeInfo)751 void XPCWrappedNativeScope::AddSizeOfIncludingThis(
752     ScopeSizeInfo* scopeSizeInfo) {
753   scopeSizeInfo->mScopeAndMapSize += scopeSizeInfo->mMallocSizeOf(this);
754   scopeSizeInfo->mScopeAndMapSize +=
755       mWrappedNativeMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
756   scopeSizeInfo->mScopeAndMapSize +=
757       mWrappedNativeProtoMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
758 
759   if (dom::HasProtoAndIfaceCache(mGlobalJSObject)) {
760     dom::ProtoAndIfaceCache* cache =
761         dom::GetProtoAndIfaceCache(mGlobalJSObject);
762     scopeSizeInfo->mProtoAndIfaceCacheSize +=
763         cache->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
764   }
765 
766   // There are other XPCWrappedNativeScope members that could be measured;
767   // the above ones have been seen by DMD to be worth measuring.  More stuff
768   // may be added later.
769 }
770