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