Line data Source code
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 "nsIAddonInterposition.h"
17 : #include "nsIXULRuntime.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::InterpositionMap* XPCWrappedNativeScope::gInterpositionMap = nullptr;
31 : InterpositionWhitelistArray* XPCWrappedNativeScope::gInterpositionWhitelists = nullptr;
32 : XPCWrappedNativeScope::AddonSet* XPCWrappedNativeScope::gAllowCPOWAddonSet = nullptr;
33 :
34 0 : NS_IMPL_ISUPPORTS(XPCWrappedNativeScope::ClearInterpositionsObserver, nsIObserver)
35 :
36 : NS_IMETHODIMP
37 0 : XPCWrappedNativeScope::ClearInterpositionsObserver::Observe(nsISupports* subject,
38 : const char* topic,
39 : const char16_t* data)
40 : {
41 0 : MOZ_ASSERT(strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
42 :
43 : // The interposition map holds strong references to interpositions, which
44 : // may themselves be involved in cycles. We need to drop these strong
45 : // references before the cycle collector shuts down. Otherwise we'll
46 : // leak. This observer always runs before CC shutdown.
47 0 : if (gInterpositionMap) {
48 0 : delete gInterpositionMap;
49 0 : gInterpositionMap = nullptr;
50 : }
51 :
52 0 : if (gInterpositionWhitelists) {
53 0 : delete gInterpositionWhitelists;
54 0 : gInterpositionWhitelists = nullptr;
55 : }
56 :
57 0 : if (gAllowCPOWAddonSet) {
58 0 : delete gAllowCPOWAddonSet;
59 0 : gAllowCPOWAddonSet = nullptr;
60 : }
61 :
62 0 : nsContentUtils::UnregisterShutdownObserver(this);
63 0 : return NS_OK;
64 : }
65 :
66 : static bool
67 293 : RemoteXULForbidsXBLScope(nsIPrincipal* aPrincipal, HandleObject aGlobal)
68 : {
69 293 : MOZ_ASSERT(aPrincipal);
70 :
71 : // Certain singleton sandoxes are created very early in startup - too early
72 : // to call into AllowXULXBLForPrincipal. We never create XBL scopes for
73 : // sandboxes anway, and certainly not for these singleton scopes. So we just
74 : // short-circuit here.
75 293 : if (IsSandbox(aGlobal))
76 25 : return false;
77 :
78 : // AllowXULXBLForPrincipal will return true for system principal, but we
79 : // don't want that here.
80 268 : MOZ_ASSERT(nsContentUtils::IsInitialized());
81 268 : if (nsContentUtils::IsSystemPrincipal(aPrincipal))
82 263 : return false;
83 :
84 : // If this domain isn't whitelisted, we're done.
85 5 : if (!nsContentUtils::AllowXULXBLForPrincipal(aPrincipal))
86 5 : return false;
87 :
88 : // Check the pref to determine how we should behave.
89 0 : return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false);
90 : }
91 :
92 293 : XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx,
93 293 : JS::HandleObject aGlobal)
94 293 : : mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)),
95 293 : mWrappedNativeProtoMap(ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)),
96 : mComponents(nullptr),
97 : mNext(nullptr),
98 : mGlobalJSObject(aGlobal),
99 : mHasCallInterpositions(false),
100 : mIsContentXBLScope(false),
101 879 : mIsAddonScope(false)
102 : {
103 : // add ourselves to the scopes list
104 : {
105 293 : MOZ_ASSERT(aGlobal);
106 586 : DebugOnly<const js::Class*> clasp = js::GetObjectClass(aGlobal);
107 293 : MOZ_ASSERT(clasp->flags & (JSCLASS_PRIVATE_IS_NSISUPPORTS |
108 : JSCLASS_HAS_PRIVATE) ||
109 : mozilla::dom::IsDOMClass(clasp));
110 : #ifdef DEBUG
111 24755 : for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext)
112 24462 : MOZ_ASSERT(aGlobal != cur->GetGlobalJSObjectPreserveColor(), "dup object");
113 : #endif
114 :
115 293 : mNext = gScopes;
116 293 : gScopes = this;
117 : }
118 :
119 293 : MOZ_COUNT_CTOR(XPCWrappedNativeScope);
120 :
121 : // Create the compartment private.
122 293 : JSCompartment* c = js::GetObjectCompartment(aGlobal);
123 293 : MOZ_ASSERT(!JS_GetCompartmentPrivate(c));
124 293 : CompartmentPrivate* priv = new CompartmentPrivate(c);
125 293 : JS_SetCompartmentPrivate(c, priv);
126 :
127 : // Attach ourselves to the compartment private.
128 293 : priv->scope = this;
129 :
130 : // Determine whether we would allow an XBL scope in this situation.
131 : // In addition to being pref-controlled, we also disable XBL scopes for
132 : // remote XUL domains, _except_ if we have an additional pref override set.
133 293 : nsIPrincipal* principal = GetPrincipal();
134 293 : mAllowContentXBLScope = !RemoteXULForbidsXBLScope(principal, aGlobal);
135 :
136 : // Determine whether to use an XBL scope.
137 293 : mUseContentXBLScope = mAllowContentXBLScope;
138 293 : if (mUseContentXBLScope) {
139 293 : const js::Class* clasp = js::GetObjectClass(mGlobalJSObject);
140 293 : mUseContentXBLScope = !strcmp(clasp->name, "Window");
141 : }
142 293 : if (mUseContentXBLScope) {
143 7 : mUseContentXBLScope = principal && !nsContentUtils::IsSystemPrincipal(principal);
144 : }
145 :
146 293 : JSAddonId* addonId = JS::AddonIdOfObject(aGlobal);
147 293 : if (gInterpositionMap) {
148 0 : bool isSystem = nsContentUtils::IsSystemPrincipal(principal);
149 0 : bool waiveInterposition = priv->waiveInterposition;
150 0 : InterpositionMap::Ptr interposition = gInterpositionMap->lookup(addonId);
151 0 : if (!waiveInterposition && interposition) {
152 0 : MOZ_RELEASE_ASSERT(isSystem);
153 0 : mInterposition = interposition->value();
154 : }
155 : // We also want multiprocessCompatible add-ons to have a default interposition.
156 0 : if (!mInterposition && addonId && isSystem) {
157 : bool interpositionEnabled = mozilla::Preferences::GetBool(
158 0 : "extensions.interposition.enabled", false);
159 0 : if (interpositionEnabled) {
160 0 : mInterposition = do_GetService("@mozilla.org/addons/default-addon-shims;1");
161 0 : MOZ_ASSERT(mInterposition);
162 0 : UpdateInterpositionWhitelist(cx, mInterposition);
163 : }
164 : }
165 : }
166 :
167 293 : if (addonId) {
168 : // We forbid CPOWs unless they're specifically allowed.
169 29 : priv->allowCPOWs = gAllowCPOWAddonSet ? gAllowCPOWAddonSet->has(addonId) : false;
170 : }
171 293 : }
172 :
173 : // static
174 : bool
175 0 : XPCWrappedNativeScope::IsDyingScope(XPCWrappedNativeScope* scope)
176 : {
177 0 : for (XPCWrappedNativeScope* cur = gDyingScopes; cur; cur = cur->mNext) {
178 0 : if (scope == cur)
179 0 : return true;
180 : }
181 0 : return false;
182 : }
183 :
184 : bool
185 279 : XPCWrappedNativeScope::GetComponentsJSObject(JS::MutableHandleObject obj)
186 : {
187 558 : AutoJSContext cx;
188 279 : if (!mComponents) {
189 278 : nsIPrincipal* p = GetPrincipal();
190 278 : bool system = nsXPConnect::SecurityManager()->IsSystemPrincipal(p);
191 278 : mComponents = system ? new nsXPCComponents(this)
192 556 : : new nsXPCComponentsBase(this);
193 : }
194 :
195 558 : RootedValue val(cx);
196 558 : xpcObjectHelper helper(mComponents);
197 558 : bool ok = XPCConvert::NativeInterface2JSObject(&val, nullptr, helper,
198 : nullptr, false,
199 279 : nullptr);
200 279 : if (NS_WARN_IF(!ok))
201 0 : return false;
202 :
203 279 : if (NS_WARN_IF(!val.isObject()))
204 0 : return false;
205 :
206 : // The call to wrap() here is necessary even though the object is same-
207 : // compartment, because it applies our security wrapper.
208 279 : obj.set(&val.toObject());
209 279 : if (NS_WARN_IF(!JS_WrapObject(cx, obj)))
210 0 : return false;
211 279 : return true;
212 : }
213 :
214 : void
215 1 : XPCWrappedNativeScope::ForcePrivilegedComponents()
216 : {
217 2 : nsCOMPtr<nsIXPCComponents> c = do_QueryInterface(mComponents);
218 1 : if (!c)
219 1 : mComponents = new nsXPCComponents(this);
220 1 : }
221 :
222 : bool
223 279 : XPCWrappedNativeScope::AttachComponentsObject(JSContext* aCx)
224 : {
225 558 : RootedObject components(aCx);
226 279 : if (!GetComponentsJSObject(&components))
227 0 : return false;
228 :
229 558 : RootedObject global(aCx, GetGlobalJSObject());
230 279 : MOZ_ASSERT(js::IsObjectInContextCompartment(global, aCx));
231 :
232 : // The global Components property is non-configurable if it's a full
233 : // nsXPCComponents object. That way, if it's an nsXPCComponentsBase,
234 : // enableUniversalXPConnect can upgrade it later.
235 279 : unsigned attrs = JSPROP_READONLY | JSPROP_RESOLVING;
236 558 : nsCOMPtr<nsIXPCComponents> c = do_QueryInterface(mComponents);
237 279 : if (c)
238 279 : attrs |= JSPROP_PERMANENT;
239 :
240 558 : RootedId id(aCx, XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS));
241 279 : return JS_DefinePropertyById(aCx, global, id, components, attrs);
242 : }
243 :
244 : static bool
245 0 : CompartmentPerAddon()
246 : {
247 : static bool initialized = false;
248 : static bool pref = false;
249 :
250 0 : if (!initialized) {
251 0 : pref = Preferences::GetBool("dom.compartment_per_addon", false) ||
252 0 : BrowserTabsRemoteAutostart();
253 0 : initialized = true;
254 : }
255 :
256 0 : return pref;
257 : }
258 :
259 : JSObject*
260 2698 : XPCWrappedNativeScope::EnsureContentXBLScope(JSContext* cx)
261 : {
262 5396 : JS::RootedObject global(cx, GetGlobalJSObject());
263 2698 : MOZ_ASSERT(js::IsObjectInContextCompartment(global, cx));
264 2698 : MOZ_ASSERT(!mIsContentXBLScope);
265 2698 : MOZ_ASSERT(strcmp(js::GetObjectClass(global)->name,
266 : "nsXBLPrototypeScript compilation scope"));
267 :
268 : // If we already have a special XBL scope object, we know what to use.
269 2698 : if (mContentXBLScope)
270 0 : return mContentXBLScope;
271 :
272 : // If this scope doesn't need an XBL scope, just return the global.
273 2698 : if (!mUseContentXBLScope)
274 2698 : return global;
275 :
276 : // Set up the sandbox options. Note that we use the DOM global as the
277 : // sandboxPrototype so that the XBL scope can access all the DOM objects
278 : // it's accustomed to accessing.
279 : //
280 : // In general wantXrays shouldn't matter much here, but there are weird
281 : // cases when adopting bound content between same-origin globals where a
282 : // <destructor> in one content XBL scope sees anonymous content in another
283 : // content XBL scope. When that happens, we hit LookupBindingMember for an
284 : // anonymous element that lives in a content XBL scope, which isn't a tested
285 : // or audited codepath. So let's avoid hitting that case by opting out of
286 : // same-origin Xrays.
287 0 : SandboxOptions options;
288 0 : options.wantXrays = false;
289 0 : options.wantComponents = true;
290 0 : options.proto = global;
291 0 : options.sameZoneAs = global;
292 :
293 : // Use an ExpandedPrincipal to create asymmetric security.
294 0 : nsIPrincipal* principal = GetPrincipal();
295 0 : MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal));
296 0 : nsTArray<nsCOMPtr<nsIPrincipal>> principalAsArray(1);
297 0 : principalAsArray.AppendElement(principal);
298 : RefPtr<ExpandedPrincipal> ep =
299 0 : ExpandedPrincipal::Create(principalAsArray,
300 0 : principal->OriginAttributesRef());
301 :
302 : // Create the sandbox.
303 0 : RootedValue v(cx);
304 0 : nsresult rv = CreateSandboxObject(cx, &v,
305 0 : static_cast<nsIExpandedPrincipal*>(ep),
306 0 : options);
307 0 : NS_ENSURE_SUCCESS(rv, nullptr);
308 0 : mContentXBLScope = &v.toObject();
309 :
310 : // Tag it.
311 0 : CompartmentPrivate::Get(js::UncheckedUnwrap(mContentXBLScope))->scope->mIsContentXBLScope = true;
312 :
313 : // Good to go!
314 0 : return mContentXBLScope;
315 : }
316 :
317 : bool
318 5350 : XPCWrappedNativeScope::AllowContentXBLScope()
319 : {
320 : // We only disallow XBL scopes in remote XUL situations.
321 5350 : MOZ_ASSERT_IF(!mAllowContentXBLScope,
322 : nsContentUtils::AllowXULXBLForPrincipal(GetPrincipal()));
323 5350 : return mAllowContentXBLScope;
324 : }
325 :
326 : namespace xpc {
327 : JSObject*
328 2698 : GetXBLScope(JSContext* cx, JSObject* contentScopeArg)
329 : {
330 2698 : MOZ_ASSERT(!IsInAddonScope(contentScopeArg));
331 :
332 5396 : JS::RootedObject contentScope(cx, contentScopeArg);
333 5396 : JSAutoCompartment ac(cx, contentScope);
334 2698 : JSObject* scope = CompartmentPrivate::Get(contentScope)->scope->EnsureContentXBLScope(cx);
335 2698 : NS_ENSURE_TRUE(scope, nullptr); // See bug 858642.
336 2698 : scope = js::UncheckedUnwrap(scope);
337 2698 : JS::ExposeObjectToActiveJS(scope);
338 2698 : return scope;
339 : }
340 :
341 : JSObject*
342 230 : GetScopeForXBLExecution(JSContext* cx, HandleObject contentScope, JSAddonId* addonId)
343 : {
344 230 : MOZ_RELEASE_ASSERT(!IsInAddonScope(contentScope));
345 :
346 460 : RootedObject global(cx, js::GetGlobalForObjectCrossCompartment(contentScope));
347 230 : if (IsInContentXBLScope(contentScope))
348 0 : return global;
349 :
350 460 : JSAutoCompartment ac(cx, contentScope);
351 230 : XPCWrappedNativeScope* nativeScope = CompartmentPrivate::Get(contentScope)->scope;
352 230 : bool isSystem = nsContentUtils::IsSystemPrincipal(nativeScope->GetPrincipal());
353 :
354 460 : RootedObject scope(cx);
355 230 : if (nativeScope->UseContentXBLScope())
356 0 : scope = nativeScope->EnsureContentXBLScope(cx);
357 230 : else if (addonId && CompartmentPerAddon() && isSystem)
358 0 : scope = nativeScope->EnsureAddonScope(cx, addonId);
359 : else
360 230 : scope = global;
361 :
362 230 : NS_ENSURE_TRUE(scope, nullptr); // See bug 858642.
363 230 : scope = js::UncheckedUnwrap(scope);
364 230 : JS::ExposeObjectToActiveJS(scope);
365 230 : return scope;
366 : }
367 :
368 : bool
369 5350 : AllowContentXBLScope(JSCompartment* c)
370 : {
371 5350 : XPCWrappedNativeScope* scope = CompartmentPrivate::Get(c)->scope;
372 5350 : return scope && scope->AllowContentXBLScope();
373 : }
374 :
375 : bool
376 638 : UseContentXBLScope(JSCompartment* c)
377 : {
378 638 : XPCWrappedNativeScope* scope = CompartmentPrivate::Get(c)->scope;
379 638 : return scope && scope->UseContentXBLScope();
380 : }
381 :
382 : void
383 1 : ClearContentXBLScope(JSObject* global)
384 : {
385 1 : CompartmentPrivate::Get(global)->scope->ClearContentXBLScope();
386 1 : }
387 :
388 : } /* namespace xpc */
389 :
390 : JSObject*
391 0 : XPCWrappedNativeScope::EnsureAddonScope(JSContext* cx, JSAddonId* addonId)
392 : {
393 0 : JS::RootedObject global(cx, GetGlobalJSObject());
394 0 : MOZ_ASSERT(js::IsObjectInContextCompartment(global, cx));
395 0 : MOZ_ASSERT(!mIsContentXBLScope);
396 0 : MOZ_ASSERT(!mIsAddonScope);
397 0 : MOZ_ASSERT(addonId);
398 0 : MOZ_ASSERT(nsContentUtils::IsSystemPrincipal(GetPrincipal()));
399 :
400 : // In bug 1092156, we found that add-on scopes don't work correctly when the
401 : // window navigates. The add-on global's prototype is an outer window, so,
402 : // after the navigation, looking up window properties in the add-on scope
403 : // will fail. However, in most cases where the window can be navigated, the
404 : // entire window is part of the add-on. To solve the problem, we avoid
405 : // returning an add-on scope for a window that is already tagged with the
406 : // add-on ID.
407 0 : if (AddonIdOfObject(global) == addonId)
408 0 : return global;
409 :
410 : // If we already have an addon scope object, we know what to use.
411 0 : for (size_t i = 0; i < mAddonScopes.Length(); i++) {
412 0 : if (JS::AddonIdOfObject(js::UncheckedUnwrap(mAddonScopes[i])) == addonId)
413 0 : return mAddonScopes[i];
414 : }
415 :
416 0 : SandboxOptions options;
417 0 : options.wantComponents = true;
418 0 : options.proto = global;
419 0 : options.sameZoneAs = global;
420 0 : options.addonId = JS::StringOfAddonId(addonId);
421 0 : options.writeToGlobalPrototype = true;
422 :
423 0 : RootedValue v(cx);
424 0 : nsresult rv = CreateSandboxObject(cx, &v, GetPrincipal(), options);
425 0 : NS_ENSURE_SUCCESS(rv, nullptr);
426 0 : mAddonScopes.AppendElement(&v.toObject());
427 :
428 0 : CompartmentPrivate::Get(js::UncheckedUnwrap(&v.toObject()))->scope->mIsAddonScope = true;
429 0 : return &v.toObject();
430 : }
431 :
432 : JSObject*
433 39 : xpc::GetAddonScope(JSContext* cx, JS::HandleObject contentScope, JSAddonId* addonId)
434 : {
435 39 : MOZ_RELEASE_ASSERT(!IsInAddonScope(contentScope));
436 :
437 39 : if (!addonId || !CompartmentPerAddon()) {
438 39 : return js::GetGlobalForObjectCrossCompartment(contentScope);
439 : }
440 :
441 0 : JSAutoCompartment ac(cx, contentScope);
442 0 : XPCWrappedNativeScope* nativeScope = CompartmentPrivate::Get(contentScope)->scope;
443 0 : if (nativeScope->GetPrincipal() != nsXPConnect::SystemPrincipal()) {
444 : // This can happen if, for example, Jetpack loads an unprivileged HTML
445 : // page from the add-on. It's not clear what to do there, so we just use
446 : // the normal global.
447 0 : return js::GetGlobalForObjectCrossCompartment(contentScope);
448 : }
449 0 : JSObject* scope = nativeScope->EnsureAddonScope(cx, addonId);
450 0 : NS_ENSURE_TRUE(scope, nullptr);
451 :
452 0 : scope = js::UncheckedUnwrap(scope);
453 0 : JS::ExposeObjectToActiveJS(scope);
454 0 : return scope;
455 : }
456 :
457 0 : XPCWrappedNativeScope::~XPCWrappedNativeScope()
458 : {
459 0 : MOZ_COUNT_DTOR(XPCWrappedNativeScope);
460 :
461 : // We can do additional cleanup assertions here...
462 :
463 0 : MOZ_ASSERT(0 == mWrappedNativeMap->Count(), "scope has non-empty map");
464 0 : delete mWrappedNativeMap;
465 :
466 0 : MOZ_ASSERT(0 == mWrappedNativeProtoMap->Count(), "scope has non-empty map");
467 0 : delete mWrappedNativeProtoMap;
468 :
469 : // This should not be necessary, since the Components object should die
470 : // with the scope but just in case.
471 0 : if (mComponents)
472 0 : mComponents->mScope = nullptr;
473 :
474 : // XXX we should assert that we are dead or that xpconnect has shutdown
475 : // XXX might not want to do this at xpconnect shutdown time???
476 0 : mComponents = nullptr;
477 :
478 0 : if (mXrayExpandos.initialized())
479 0 : mXrayExpandos.destroy();
480 :
481 0 : JSContext* cx = dom::danger::GetJSContext();
482 0 : mGlobalJSObject.finalize(cx);
483 0 : }
484 :
485 : // static
486 : void
487 1 : XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(JSTracer* trc)
488 : {
489 : // Do JS::TraceEdge for all wrapped natives with external references, as
490 : // well as any DOM expando objects.
491 212 : for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
492 4687 : for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
493 4476 : auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
494 4476 : XPCWrappedNative* wrapper = entry->value;
495 4476 : if (wrapper->HasExternalReference() && !wrapper->IsWrapperExpired())
496 2 : wrapper->TraceSelf(trc);
497 : }
498 : }
499 1 : }
500 :
501 : // static
502 : void
503 0 : XPCWrappedNativeScope::SuspectAllWrappers(nsCycleCollectionNoteRootCallback& cb)
504 : {
505 0 : for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
506 0 : for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
507 0 : static_cast<Native2WrappedNativeMap::Entry*>(i.Get())->value->Suspect(cb);
508 : }
509 : }
510 0 : }
511 :
512 : // static
513 : void
514 0 : XPCWrappedNativeScope::UpdateWeakPointersInAllScopesAfterGC()
515 : {
516 : // If this is called from the finalization callback in JSGC_MARK_END then
517 : // JSGC_FINALIZE_END must always follow it calling
518 : // FinishedFinalizationPhaseOfGC and clearing gDyingScopes in
519 : // KillDyingScopes.
520 0 : MOZ_ASSERT(!gDyingScopes, "JSGC_MARK_END without JSGC_FINALIZE_END");
521 :
522 0 : XPCWrappedNativeScope** scopep = &gScopes;
523 0 : while (*scopep) {
524 0 : XPCWrappedNativeScope* cur = *scopep;
525 0 : cur->UpdateWeakPointersAfterGC();
526 0 : if (cur->mGlobalJSObject) {
527 0 : scopep = &cur->mNext;
528 : } else {
529 : // The scope's global is dead so move it to the dying scopes list.
530 0 : *scopep = cur->mNext;
531 0 : cur->mNext = gDyingScopes;
532 0 : gDyingScopes = cur;
533 : }
534 : }
535 0 : }
536 :
537 : static inline void
538 0 : AssertSameCompartment(DebugOnly<JSCompartment*>& comp, JSObject* obj)
539 : {
540 0 : MOZ_ASSERT_IF(obj, js::GetObjectCompartment(obj) == comp);
541 0 : }
542 :
543 : static inline void
544 0 : AssertSameCompartment(DebugOnly<JSCompartment*>& comp, const JS::ObjectPtr& obj)
545 : {
546 : #ifdef DEBUG
547 0 : AssertSameCompartment(comp, obj.unbarrieredGet());
548 : #endif
549 0 : }
550 :
551 : void
552 0 : XPCWrappedNativeScope::UpdateWeakPointersAfterGC()
553 : {
554 : // Sweep waivers.
555 0 : if (mWaiverWrapperMap)
556 0 : mWaiverWrapperMap->Sweep();
557 :
558 0 : if (!js::IsObjectZoneSweepingOrCompacting(mGlobalJSObject.unbarrieredGet()))
559 0 : return;
560 :
561 : // Update our pointer to the global object in case it was moved or
562 : // finalized.
563 0 : mGlobalJSObject.updateWeakPointerAfterGC();
564 0 : if (!mGlobalJSObject) {
565 0 : JSContext* cx = dom::danger::GetJSContext();
566 0 : mContentXBLScope.finalize(cx);
567 0 : for (size_t i = 0; i < mAddonScopes.Length(); i++)
568 0 : mAddonScopes[i].finalize(cx);
569 0 : GetWrappedNativeMap()->Clear();
570 0 : mWrappedNativeProtoMap->Clear();
571 0 : return;
572 : }
573 :
574 : DebugOnly<JSCompartment*> comp =
575 0 : js::GetObjectCompartment(mGlobalJSObject.unbarrieredGet());
576 :
577 : #ifdef DEBUG
578 : // These are traced, so no updates are necessary.
579 0 : if (mContentXBLScope) {
580 0 : JSObject* prev = mContentXBLScope.unbarrieredGet();
581 0 : mContentXBLScope.updateWeakPointerAfterGC();
582 0 : MOZ_ASSERT(prev == mContentXBLScope.unbarrieredGet());
583 0 : AssertSameCompartment(comp, mContentXBLScope);
584 : }
585 0 : for (size_t i = 0; i < mAddonScopes.Length(); i++) {
586 0 : JSObject* prev = mAddonScopes[i].unbarrieredGet();
587 0 : mAddonScopes[i].updateWeakPointerAfterGC();
588 0 : MOZ_ASSERT(prev == mAddonScopes[i].unbarrieredGet());
589 0 : AssertSameCompartment(comp, mAddonScopes[i]);
590 : }
591 : #endif
592 :
593 : // Sweep mWrappedNativeMap for dying flat JS objects. Moving has already
594 : // been handled by XPCWrappedNative::FlatJSObjectMoved.
595 0 : for (auto iter = GetWrappedNativeMap()->Iter(); !iter.Done(); iter.Next()) {
596 0 : auto entry = static_cast<Native2WrappedNativeMap::Entry*>(iter.Get());
597 0 : XPCWrappedNative* wrapper = entry->value;
598 0 : JSObject* obj = wrapper->GetFlatJSObjectPreserveColor();
599 0 : JS_UpdateWeakPointerAfterGCUnbarriered(&obj);
600 0 : MOZ_ASSERT(!obj || obj == wrapper->GetFlatJSObjectPreserveColor());
601 0 : AssertSameCompartment(comp, obj);
602 0 : if (!obj)
603 0 : iter.Remove();
604 : }
605 :
606 : // Sweep mWrappedNativeProtoMap for dying prototype JSObjects. Moving has
607 : // already been handled by XPCWrappedNativeProto::JSProtoObjectMoved.
608 0 : for (auto i = mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
609 0 : auto entry = static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
610 0 : JSObject* obj = entry->value->GetJSProtoObjectPreserveColor();
611 0 : JS_UpdateWeakPointerAfterGCUnbarriered(&obj);
612 0 : AssertSameCompartment(comp, obj);
613 0 : MOZ_ASSERT(!obj || obj == entry->value->GetJSProtoObjectPreserveColor());
614 0 : if (!obj)
615 0 : i.Remove();
616 : }
617 : }
618 :
619 : // static
620 : void
621 0 : XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs()
622 : {
623 0 : for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
624 0 : for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
625 0 : auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
626 0 : entry->value->SweepTearOffs();
627 : }
628 : }
629 0 : }
630 :
631 : // static
632 : void
633 0 : XPCWrappedNativeScope::KillDyingScopes()
634 : {
635 0 : XPCWrappedNativeScope* cur = gDyingScopes;
636 0 : while (cur) {
637 0 : XPCWrappedNativeScope* next = cur->mNext;
638 0 : if (cur->mGlobalJSObject)
639 0 : CompartmentPrivate::Get(cur->mGlobalJSObject)->scope = nullptr;
640 0 : delete cur;
641 0 : cur = next;
642 : }
643 0 : gDyingScopes = nullptr;
644 0 : }
645 :
646 : //static
647 : void
648 0 : XPCWrappedNativeScope::SystemIsBeingShutDown()
649 : {
650 0 : int liveScopeCount = 0;
651 :
652 : XPCWrappedNativeScope* cur;
653 :
654 : // First move all the scopes to the dying list.
655 :
656 0 : cur = gScopes;
657 0 : while (cur) {
658 0 : XPCWrappedNativeScope* next = cur->mNext;
659 0 : cur->mNext = gDyingScopes;
660 0 : gDyingScopes = cur;
661 0 : cur = next;
662 0 : liveScopeCount++;
663 : }
664 0 : gScopes = nullptr;
665 :
666 : // We're forcibly killing scopes, rather than allowing them to go away
667 : // when they're ready. As such, we need to do some cleanup before they
668 : // can safely be destroyed.
669 :
670 0 : for (cur = gDyingScopes; cur; cur = cur->mNext) {
671 : // Give the Components object a chance to try to clean up.
672 0 : if (cur->mComponents)
673 0 : cur->mComponents->SystemIsBeingShutDown();
674 :
675 : // Walk the protos first. Wrapper shutdown can leave dangling
676 : // proto pointers in the proto map.
677 0 : for (auto i = cur->mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
678 0 : auto entry = static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
679 0 : entry->value->SystemIsBeingShutDown();
680 0 : i.Remove();
681 : }
682 0 : for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
683 0 : auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
684 0 : XPCWrappedNative* wrapper = entry->value;
685 0 : if (wrapper->IsValid()) {
686 0 : wrapper->SystemIsBeingShutDown();
687 : }
688 0 : i.Remove();
689 : }
690 : }
691 :
692 : // Now it is safe to kill all the scopes.
693 0 : KillDyingScopes();
694 0 : }
695 :
696 :
697 : /***************************************************************************/
698 :
699 : JSObject*
700 1181 : XPCWrappedNativeScope::GetExpandoChain(HandleObject target)
701 : {
702 1181 : MOZ_ASSERT(ObjectScope(target) == this);
703 1181 : if (!mXrayExpandos.initialized())
704 1181 : return nullptr;
705 0 : return mXrayExpandos.lookup(target);
706 : }
707 :
708 : bool
709 0 : XPCWrappedNativeScope::SetExpandoChain(JSContext* cx, HandleObject target,
710 : HandleObject chain)
711 : {
712 0 : MOZ_ASSERT(ObjectScope(target) == this);
713 0 : MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx));
714 0 : MOZ_ASSERT_IF(chain, ObjectScope(chain) == this);
715 0 : if (!mXrayExpandos.initialized() && !mXrayExpandos.init(cx))
716 0 : return false;
717 0 : return mXrayExpandos.put(cx, target, chain);
718 : }
719 :
720 : /* static */ bool
721 0 : XPCWrappedNativeScope::SetAddonInterposition(JSContext* cx,
722 : JSAddonId* addonId,
723 : nsIAddonInterposition* interp)
724 : {
725 0 : if (!gInterpositionMap) {
726 0 : gInterpositionMap = new InterpositionMap();
727 0 : bool ok = gInterpositionMap->init();
728 0 : NS_ENSURE_TRUE(ok, false);
729 :
730 0 : if (!gShutdownObserverInitialized) {
731 0 : gShutdownObserverInitialized = true;
732 0 : nsContentUtils::RegisterShutdownObserver(new ClearInterpositionsObserver());
733 : }
734 : }
735 0 : if (interp) {
736 0 : bool ok = gInterpositionMap->put(addonId, interp);
737 0 : NS_ENSURE_TRUE(ok, false);
738 0 : UpdateInterpositionWhitelist(cx, interp);
739 : } else {
740 0 : gInterpositionMap->remove(addonId);
741 : }
742 0 : return true;
743 : }
744 :
745 : /* static */ bool
746 0 : XPCWrappedNativeScope::AllowCPOWsInAddon(JSContext* cx,
747 : JSAddonId* addonId,
748 : bool allow)
749 : {
750 0 : if (!gAllowCPOWAddonSet) {
751 0 : gAllowCPOWAddonSet = new AddonSet();
752 0 : bool ok = gAllowCPOWAddonSet->init();
753 0 : NS_ENSURE_TRUE(ok, false);
754 :
755 0 : if (!gShutdownObserverInitialized) {
756 0 : gShutdownObserverInitialized = true;
757 0 : nsContentUtils::RegisterShutdownObserver(new ClearInterpositionsObserver());
758 : }
759 : }
760 0 : if (allow) {
761 0 : bool ok = gAllowCPOWAddonSet->put(addonId);
762 0 : NS_ENSURE_TRUE(ok, false);
763 : } else {
764 0 : gAllowCPOWAddonSet->remove(addonId);
765 : }
766 0 : return true;
767 : }
768 :
769 : nsCOMPtr<nsIAddonInterposition>
770 0 : XPCWrappedNativeScope::GetInterposition()
771 : {
772 0 : return mInterposition;
773 : }
774 :
775 : /* static */ InterpositionWhitelist*
776 0 : XPCWrappedNativeScope::GetInterpositionWhitelist(nsIAddonInterposition* interposition)
777 : {
778 0 : if (!gInterpositionWhitelists)
779 0 : return nullptr;
780 :
781 0 : InterpositionWhitelistArray& wls = *gInterpositionWhitelists;
782 0 : for (size_t i = 0; i < wls.Length(); i++) {
783 0 : if (wls[i].interposition == interposition)
784 0 : return &wls[i].whitelist;
785 : }
786 :
787 0 : return nullptr;
788 : }
789 :
790 : /* static */ bool
791 0 : XPCWrappedNativeScope::UpdateInterpositionWhitelist(JSContext* cx,
792 : nsIAddonInterposition* interposition)
793 : {
794 : // We want to set the interpostion whitelist only once.
795 0 : InterpositionWhitelist* whitelist = GetInterpositionWhitelist(interposition);
796 0 : if (whitelist)
797 0 : return true;
798 :
799 : // The hashsets in gInterpositionWhitelists do not have a copy constructor so
800 : // a reallocation for the array will lead to a memory corruption. If you
801 : // need more interpositions, change the capacity of the array please.
802 : static const size_t MAX_INTERPOSITION = 8;
803 0 : if (!gInterpositionWhitelists)
804 0 : gInterpositionWhitelists = new InterpositionWhitelistArray(MAX_INTERPOSITION);
805 :
806 0 : MOZ_RELEASE_ASSERT(MAX_INTERPOSITION > gInterpositionWhitelists->Length() + 1);
807 0 : InterpositionWhitelistPair* newPair = gInterpositionWhitelists->AppendElement();
808 0 : newPair->interposition = interposition;
809 0 : if (!newPair->whitelist.init()) {
810 0 : JS_ReportOutOfMemory(cx);
811 0 : return false;
812 : }
813 :
814 0 : whitelist = &newPair->whitelist;
815 :
816 0 : RootedValue whitelistVal(cx);
817 0 : nsresult rv = interposition->GetWhitelist(&whitelistVal);
818 0 : if (NS_FAILED(rv)) {
819 0 : JS_ReportErrorASCII(cx, "Could not get the whitelist from the interposition.");
820 0 : return false;
821 : }
822 :
823 0 : if (!whitelistVal.isObject()) {
824 0 : JS_ReportErrorASCII(cx, "Whitelist must be an array.");
825 0 : return false;
826 : }
827 :
828 : // We want to enter the whitelist's compartment to avoid any wrappers.
829 : // To be on the safe side let's make sure that it's a system compartment
830 : // and we don't accidentally trigger some content function here by parsing
831 : // the whitelist object.
832 0 : RootedObject whitelistObj(cx, &whitelistVal.toObject());
833 0 : whitelistObj = js::UncheckedUnwrap(whitelistObj);
834 0 : if (!AccessCheck::isChrome(whitelistObj)) {
835 0 : JS_ReportErrorASCII(cx, "Whitelist must be from system scope.");
836 0 : return false;
837 : }
838 :
839 : {
840 0 : JSAutoCompartment ac(cx, whitelistObj);
841 :
842 : bool isArray;
843 0 : if (!JS_IsArrayObject(cx, whitelistObj, &isArray))
844 0 : return false;
845 :
846 0 : if (!isArray) {
847 0 : JS_ReportErrorASCII(cx, "Whitelist must be an array.");
848 0 : return false;
849 : }
850 :
851 : uint32_t length;
852 0 : if (!JS_GetArrayLength(cx, whitelistObj, &length))
853 0 : return false;
854 :
855 0 : for (uint32_t i = 0; i < length; i++) {
856 0 : RootedValue idval(cx);
857 0 : if (!JS_GetElement(cx, whitelistObj, i, &idval))
858 0 : return false;
859 :
860 0 : if (!idval.isString()) {
861 0 : JS_ReportErrorASCII(cx, "Whitelist must contain strings only.");
862 0 : return false;
863 : }
864 :
865 0 : RootedString str(cx, idval.toString());
866 0 : str = JS_AtomizeAndPinJSString(cx, str);
867 0 : if (!str) {
868 0 : JS_ReportErrorASCII(cx, "String internization failed.");
869 0 : return false;
870 : }
871 :
872 : // By internizing the id's we ensure that they won't get
873 : // GCed so we can use them as hash keys.
874 0 : jsid id = INTERNED_STRING_TO_JSID(cx, str);
875 0 : if (!whitelist->put(JSID_BITS(id))) {
876 0 : JS_ReportOutOfMemory(cx);
877 0 : return false;
878 : }
879 : }
880 : }
881 :
882 0 : return true;
883 : }
884 :
885 : /***************************************************************************/
886 :
887 : // static
888 : void
889 0 : XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth)
890 : {
891 : #ifdef DEBUG
892 0 : depth-- ;
893 :
894 : // get scope count.
895 0 : int count = 0;
896 : XPCWrappedNativeScope* cur;
897 0 : for (cur = gScopes; cur; cur = cur->mNext)
898 0 : count++ ;
899 :
900 0 : XPC_LOG_ALWAYS(("chain of %d XPCWrappedNativeScope(s)", count));
901 0 : XPC_LOG_INDENT();
902 0 : XPC_LOG_ALWAYS(("gDyingScopes @ %p", gDyingScopes));
903 0 : if (depth)
904 0 : for (cur = gScopes; cur; cur = cur->mNext)
905 0 : cur->DebugDump(depth);
906 0 : XPC_LOG_OUTDENT();
907 : #endif
908 0 : }
909 :
910 : void
911 0 : XPCWrappedNativeScope::DebugDump(int16_t depth)
912 : {
913 : #ifdef DEBUG
914 0 : depth-- ;
915 0 : XPC_LOG_ALWAYS(("XPCWrappedNativeScope @ %p", this));
916 0 : XPC_LOG_INDENT();
917 0 : XPC_LOG_ALWAYS(("mNext @ %p", mNext));
918 0 : XPC_LOG_ALWAYS(("mComponents @ %p", mComponents.get()));
919 0 : XPC_LOG_ALWAYS(("mGlobalJSObject @ %p", mGlobalJSObject.get()));
920 :
921 0 : XPC_LOG_ALWAYS(("mWrappedNativeMap @ %p with %d wrappers(s)",
922 : mWrappedNativeMap, mWrappedNativeMap->Count()));
923 : // iterate contexts...
924 0 : if (depth && mWrappedNativeMap->Count()) {
925 0 : XPC_LOG_INDENT();
926 0 : for (auto i = mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
927 0 : auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
928 0 : entry->value->DebugDump(depth);
929 : }
930 0 : XPC_LOG_OUTDENT();
931 : }
932 :
933 0 : XPC_LOG_ALWAYS(("mWrappedNativeProtoMap @ %p with %d protos(s)",
934 : mWrappedNativeProtoMap,
935 : mWrappedNativeProtoMap->Count()));
936 : // iterate contexts...
937 0 : if (depth && mWrappedNativeProtoMap->Count()) {
938 0 : XPC_LOG_INDENT();
939 0 : for (auto i = mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
940 0 : auto entry = static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
941 0 : entry->value->DebugDump(depth);
942 : }
943 0 : XPC_LOG_OUTDENT();
944 : }
945 0 : XPC_LOG_OUTDENT();
946 : #endif
947 0 : }
948 :
949 : void
950 0 : XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(ScopeSizeInfo* scopeSizeInfo)
951 : {
952 0 : for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext)
953 0 : cur->AddSizeOfIncludingThis(scopeSizeInfo);
954 0 : }
955 :
956 : void
957 0 : XPCWrappedNativeScope::AddSizeOfIncludingThis(ScopeSizeInfo* scopeSizeInfo)
958 : {
959 0 : scopeSizeInfo->mScopeAndMapSize += scopeSizeInfo->mMallocSizeOf(this);
960 0 : scopeSizeInfo->mScopeAndMapSize +=
961 0 : mWrappedNativeMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
962 0 : scopeSizeInfo->mScopeAndMapSize +=
963 0 : mWrappedNativeProtoMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
964 :
965 0 : if (dom::HasProtoAndIfaceCache(mGlobalJSObject)) {
966 0 : dom::ProtoAndIfaceCache* cache = dom::GetProtoAndIfaceCache(mGlobalJSObject);
967 0 : scopeSizeInfo->mProtoAndIfaceCacheSize +=
968 0 : cache->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
969 : }
970 :
971 : // There are other XPCWrappedNativeScope members that could be measured;
972 : // the above ones have been seen by DMD to be worth measuring. More stuff
973 : // may be added later.
974 0 : }
|