/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set ts=8 sts=4 et sw=4 tw=99: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Class used to manage the wrapped native objects within a JS scope. */ #include "xpcprivate.h" #include "XPCWrapper.h" #include "nsContentUtils.h" #include "nsCycleCollectionNoteRootCallback.h" #include "nsPrincipal.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Preferences.h" #include "nsIAddonInterposition.h" #include "nsIXULRuntime.h" #include "mozilla/dom/BindingUtils.h" using namespace mozilla; using namespace xpc; using namespace JS; /***************************************************************************/ XPCWrappedNativeScope* XPCWrappedNativeScope::gScopes = nullptr; XPCWrappedNativeScope* XPCWrappedNativeScope::gDyingScopes = nullptr; bool XPCWrappedNativeScope::gShutdownObserverInitialized = false; XPCWrappedNativeScope::InterpositionMap* XPCWrappedNativeScope::gInterpositionMap = nullptr; InterpositionWhitelistArray* XPCWrappedNativeScope::gInterpositionWhitelists = nullptr; XPCWrappedNativeScope::AddonSet* XPCWrappedNativeScope::gAllowCPOWAddonSet = nullptr; NS_IMPL_ISUPPORTS(XPCWrappedNativeScope::ClearInterpositionsObserver, nsIObserver) NS_IMETHODIMP XPCWrappedNativeScope::ClearInterpositionsObserver::Observe(nsISupports* subject, const char* topic, const char16_t* data) { MOZ_ASSERT(strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0); // The interposition map holds strong references to interpositions, which // may themselves be involved in cycles. We need to drop these strong // references before the cycle collector shuts down. Otherwise we'll // leak. This observer always runs before CC shutdown. if (gInterpositionMap) { delete gInterpositionMap; gInterpositionMap = nullptr; } if (gInterpositionWhitelists) { delete gInterpositionWhitelists; gInterpositionWhitelists = nullptr; } if (gAllowCPOWAddonSet) { delete gAllowCPOWAddonSet; gAllowCPOWAddonSet = nullptr; } nsContentUtils::UnregisterShutdownObserver(this); return NS_OK; } static bool RemoteXULForbidsXBLScope(nsIPrincipal* aPrincipal, HandleObject aGlobal) { MOZ_ASSERT(aPrincipal); // Certain singleton sandoxes are created very early in startup - too early // to call into AllowXULXBLForPrincipal. We never create XBL scopes for // sandboxes anway, and certainly not for these singleton scopes. So we just // short-circuit here. if (IsSandbox(aGlobal)) return false; // AllowXULXBLForPrincipal will return true for system principal, but we // don't want that here. MOZ_ASSERT(nsContentUtils::IsInitialized()); if (nsContentUtils::IsSystemPrincipal(aPrincipal)) return false; // If this domain isn't whitelisted, we're done. if (!nsContentUtils::AllowXULXBLForPrincipal(aPrincipal)) return false; // Check the pref to determine how we should behave. return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false); } XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx, JS::HandleObject aGlobal) : mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)), mWrappedNativeProtoMap(ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)), mComponents(nullptr), mNext(nullptr), mGlobalJSObject(aGlobal), mHasCallInterpositions(false), mIsContentXBLScope(false), mIsAddonScope(false) { // add ourselves to the scopes list { MOZ_ASSERT(aGlobal); DebugOnly clasp = js::GetObjectClass(aGlobal); MOZ_ASSERT(clasp->flags & (JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_HAS_PRIVATE) || mozilla::dom::IsDOMClass(clasp)); #ifdef DEBUG for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) MOZ_ASSERT(aGlobal != cur->GetGlobalJSObjectPreserveColor(), "dup object"); #endif mNext = gScopes; gScopes = this; } MOZ_COUNT_CTOR(XPCWrappedNativeScope); // Create the compartment private. JSCompartment* c = js::GetObjectCompartment(aGlobal); MOZ_ASSERT(!JS_GetCompartmentPrivate(c)); CompartmentPrivate* priv = new CompartmentPrivate(c); JS_SetCompartmentPrivate(c, priv); // Attach ourselves to the compartment private. priv->scope = this; // Determine whether we would allow an XBL scope in this situation. // In addition to being pref-controlled, we also disable XBL scopes for // remote XUL domains, _except_ if we have an additional pref override set. nsIPrincipal* principal = GetPrincipal(); mAllowContentXBLScope = !RemoteXULForbidsXBLScope(principal, aGlobal); // Determine whether to use an XBL scope. mUseContentXBLScope = mAllowContentXBLScope; if (mUseContentXBLScope) { const js::Class* clasp = js::GetObjectClass(mGlobalJSObject); mUseContentXBLScope = !strcmp(clasp->name, "Window"); } if (mUseContentXBLScope) { mUseContentXBLScope = principal && !nsContentUtils::IsSystemPrincipal(principal); } JSAddonId* addonId = JS::AddonIdOfObject(aGlobal); if (gInterpositionMap) { bool isSystem = nsContentUtils::IsSystemPrincipal(principal); bool waiveInterposition = priv->waiveInterposition; InterpositionMap::Ptr interposition = gInterpositionMap->lookup(addonId); if (!waiveInterposition && interposition) { MOZ_RELEASE_ASSERT(isSystem); mInterposition = interposition->value(); } // We also want multiprocessCompatible add-ons to have a default interposition. if (!mInterposition && addonId && isSystem) { bool interpositionEnabled = mozilla::Preferences::GetBool( "extensions.interposition.enabled", false); if (interpositionEnabled) { mInterposition = do_GetService("@mozilla.org/addons/default-addon-shims;1"); MOZ_ASSERT(mInterposition); UpdateInterpositionWhitelist(cx, mInterposition); } } } if (addonId) { // We forbid CPOWs unless they're specifically allowed. priv->allowCPOWs = gAllowCPOWAddonSet ? gAllowCPOWAddonSet->has(addonId) : false; } } // static bool XPCWrappedNativeScope::IsDyingScope(XPCWrappedNativeScope* scope) { for (XPCWrappedNativeScope* cur = gDyingScopes; cur; cur = cur->mNext) { if (scope == cur) return true; } return false; } bool XPCWrappedNativeScope::GetComponentsJSObject(JS::MutableHandleObject obj) { AutoJSContext cx; if (!mComponents) { nsIPrincipal* p = GetPrincipal(); bool system = nsXPConnect::SecurityManager()->IsSystemPrincipal(p); mComponents = system ? new nsXPCComponents(this) : new nsXPCComponentsBase(this); } RootedValue val(cx); xpcObjectHelper helper(mComponents); bool ok = XPCConvert::NativeInterface2JSObject(&val, nullptr, helper, nullptr, false, nullptr); if (NS_WARN_IF(!ok)) return false; if (NS_WARN_IF(!val.isObject())) return false; // The call to wrap() here is necessary even though the object is same- // compartment, because it applies our security wrapper. obj.set(&val.toObject()); if (NS_WARN_IF(!JS_WrapObject(cx, obj))) return false; return true; } void XPCWrappedNativeScope::ForcePrivilegedComponents() { nsCOMPtr c = do_QueryInterface(mComponents); if (!c) mComponents = new nsXPCComponents(this); } bool XPCWrappedNativeScope::AttachComponentsObject(JSContext* aCx) { RootedObject components(aCx); if (!GetComponentsJSObject(&components)) return false; RootedObject global(aCx, GetGlobalJSObject()); MOZ_ASSERT(js::IsObjectInContextCompartment(global, aCx)); // The global Components property is non-configurable if it's a full // nsXPCComponents object. That way, if it's an nsXPCComponentsBase, // enableUniversalXPConnect can upgrade it later. unsigned attrs = JSPROP_READONLY | JSPROP_RESOLVING; nsCOMPtr c = do_QueryInterface(mComponents); if (c) attrs |= JSPROP_PERMANENT; RootedId id(aCx, XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)); return JS_DefinePropertyById(aCx, global, id, components, attrs); } static bool CompartmentPerAddon() { static bool initialized = false; static bool pref = false; if (!initialized) { pref = Preferences::GetBool("dom.compartment_per_addon", false) || BrowserTabsRemoteAutostart(); initialized = true; } return pref; } JSObject* XPCWrappedNativeScope::EnsureContentXBLScope(JSContext* cx) { JS::RootedObject global(cx, GetGlobalJSObject()); MOZ_ASSERT(js::IsObjectInContextCompartment(global, cx)); MOZ_ASSERT(!mIsContentXBLScope); MOZ_ASSERT(strcmp(js::GetObjectClass(global)->name, "nsXBLPrototypeScript compilation scope")); // If we already have a special XBL scope object, we know what to use. if (mContentXBLScope) return mContentXBLScope; // If this scope doesn't need an XBL scope, just return the global. if (!mUseContentXBLScope) return global; // Set up the sandbox options. Note that we use the DOM global as the // sandboxPrototype so that the XBL scope can access all the DOM objects // it's accustomed to accessing. // // In general wantXrays shouldn't matter much here, but there are weird // cases when adopting bound content between same-origin globals where a // in one content XBL scope sees anonymous content in another // content XBL scope. When that happens, we hit LookupBindingMember for an // anonymous element that lives in a content XBL scope, which isn't a tested // or audited codepath. So let's avoid hitting that case by opting out of // same-origin Xrays. SandboxOptions options; options.wantXrays = false; options.wantComponents = true; options.proto = global; options.sameZoneAs = global; // Use an nsExpandedPrincipal to create asymmetric security. nsIPrincipal* principal = GetPrincipal(); MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal)); nsTArray> principalAsArray(1); principalAsArray.AppendElement(principal); nsCOMPtr ep = new nsExpandedPrincipal(principalAsArray, BasePrincipal::Cast(principal)->OriginAttributesRef()); // Create the sandbox. RootedValue v(cx); nsresult rv = CreateSandboxObject(cx, &v, ep, options); NS_ENSURE_SUCCESS(rv, nullptr); mContentXBLScope = &v.toObject(); // Tag it. CompartmentPrivate::Get(js::UncheckedUnwrap(mContentXBLScope))->scope->mIsContentXBLScope = true; // Good to go! return mContentXBLScope; } bool XPCWrappedNativeScope::AllowContentXBLScope() { // We only disallow XBL scopes in remote XUL situations. MOZ_ASSERT_IF(!mAllowContentXBLScope, nsContentUtils::AllowXULXBLForPrincipal(GetPrincipal())); return mAllowContentXBLScope; } namespace xpc { JSObject* GetXBLScope(JSContext* cx, JSObject* contentScopeArg) { MOZ_ASSERT(!IsInAddonScope(contentScopeArg)); JS::RootedObject contentScope(cx, contentScopeArg); JSAutoCompartment ac(cx, contentScope); JSObject* scope = CompartmentPrivate::Get(contentScope)->scope->EnsureContentXBLScope(cx); NS_ENSURE_TRUE(scope, nullptr); // See bug 858642. scope = js::UncheckedUnwrap(scope); JS::ExposeObjectToActiveJS(scope); return scope; } JSObject* GetScopeForXBLExecution(JSContext* cx, HandleObject contentScope, JSAddonId* addonId) { MOZ_RELEASE_ASSERT(!IsInAddonScope(contentScope)); RootedObject global(cx, js::GetGlobalForObjectCrossCompartment(contentScope)); if (IsInContentXBLScope(contentScope)) return global; JSAutoCompartment ac(cx, contentScope); XPCWrappedNativeScope* nativeScope = CompartmentPrivate::Get(contentScope)->scope; bool isSystem = nsContentUtils::IsSystemPrincipal(nativeScope->GetPrincipal()); RootedObject scope(cx); if (nativeScope->UseContentXBLScope()) scope = nativeScope->EnsureContentXBLScope(cx); else if (addonId && CompartmentPerAddon() && isSystem) scope = nativeScope->EnsureAddonScope(cx, addonId); else scope = global; NS_ENSURE_TRUE(scope, nullptr); // See bug 858642. scope = js::UncheckedUnwrap(scope); JS::ExposeObjectToActiveJS(scope); return scope; } bool AllowContentXBLScope(JSCompartment* c) { XPCWrappedNativeScope* scope = CompartmentPrivate::Get(c)->scope; return scope && scope->AllowContentXBLScope(); } bool UseContentXBLScope(JSCompartment* c) { XPCWrappedNativeScope* scope = CompartmentPrivate::Get(c)->scope; return scope && scope->UseContentXBLScope(); } void ClearContentXBLScope(JSObject* global) { CompartmentPrivate::Get(global)->scope->ClearContentXBLScope(); } } /* namespace xpc */ JSObject* XPCWrappedNativeScope::EnsureAddonScope(JSContext* cx, JSAddonId* addonId) { JS::RootedObject global(cx, GetGlobalJSObject()); MOZ_ASSERT(js::IsObjectInContextCompartment(global, cx)); MOZ_ASSERT(!mIsContentXBLScope); MOZ_ASSERT(!mIsAddonScope); MOZ_ASSERT(addonId); MOZ_ASSERT(nsContentUtils::IsSystemPrincipal(GetPrincipal())); // In bug 1092156, we found that add-on scopes don't work correctly when the // window navigates. The add-on global's prototype is an outer window, so, // after the navigation, looking up window properties in the add-on scope // will fail. However, in most cases where the window can be navigated, the // entire window is part of the add-on. To solve the problem, we avoid // returning an add-on scope for a window that is already tagged with the // add-on ID. if (AddonIdOfObject(global) == addonId) return global; // If we already have an addon scope object, we know what to use. for (size_t i = 0; i < mAddonScopes.Length(); i++) { if (JS::AddonIdOfObject(js::UncheckedUnwrap(mAddonScopes[i])) == addonId) return mAddonScopes[i]; } SandboxOptions options; options.wantComponents = true; options.proto = global; options.sameZoneAs = global; options.addonId = JS::StringOfAddonId(addonId); options.writeToGlobalPrototype = true; RootedValue v(cx); nsresult rv = CreateSandboxObject(cx, &v, GetPrincipal(), options); NS_ENSURE_SUCCESS(rv, nullptr); mAddonScopes.AppendElement(&v.toObject()); CompartmentPrivate::Get(js::UncheckedUnwrap(&v.toObject()))->scope->mIsAddonScope = true; return &v.toObject(); } JSObject* xpc::GetAddonScope(JSContext* cx, JS::HandleObject contentScope, JSAddonId* addonId) { MOZ_RELEASE_ASSERT(!IsInAddonScope(contentScope)); if (!addonId || !CompartmentPerAddon()) { return js::GetGlobalForObjectCrossCompartment(contentScope); } JSAutoCompartment ac(cx, contentScope); XPCWrappedNativeScope* nativeScope = CompartmentPrivate::Get(contentScope)->scope; if (nativeScope->GetPrincipal() != nsXPConnect::SystemPrincipal()) { // This can happen if, for example, Jetpack loads an unprivileged HTML // page from the add-on. It's not clear what to do there, so we just use // the normal global. return js::GetGlobalForObjectCrossCompartment(contentScope); } JSObject* scope = nativeScope->EnsureAddonScope(cx, addonId); NS_ENSURE_TRUE(scope, nullptr); scope = js::UncheckedUnwrap(scope); JS::ExposeObjectToActiveJS(scope); return scope; } XPCWrappedNativeScope::~XPCWrappedNativeScope() { MOZ_COUNT_DTOR(XPCWrappedNativeScope); // We can do additional cleanup assertions here... MOZ_ASSERT(0 == mWrappedNativeMap->Count(), "scope has non-empty map"); delete mWrappedNativeMap; MOZ_ASSERT(0 == mWrappedNativeProtoMap->Count(), "scope has non-empty map"); delete mWrappedNativeProtoMap; // This should not be necessary, since the Components object should die // with the scope but just in case. if (mComponents) mComponents->mScope = nullptr; // XXX we should assert that we are dead or that xpconnect has shutdown // XXX might not want to do this at xpconnect shutdown time??? mComponents = nullptr; if (mXrayExpandos.initialized()) mXrayExpandos.destroy(); JSContext* cx = dom::danger::GetJSContext(); mContentXBLScope.finalize(cx); for (size_t i = 0; i < mAddonScopes.Length(); i++) mAddonScopes[i].finalize(cx); mGlobalJSObject.finalize(cx); } // static void XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(JSTracer* trc, XPCJSContext* cx) { // Do JS::TraceEdge for all wrapped natives with external references, as // well as any DOM expando objects. for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) { for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) { auto entry = static_cast(i.Get()); XPCWrappedNative* wrapper = entry->value; if (wrapper->HasExternalReference() && !wrapper->IsWrapperExpired()) wrapper->TraceSelf(trc); } if (cur->mDOMExpandoSet) { for (DOMExpandoSet::Enum e(*cur->mDOMExpandoSet); !e.empty(); e.popFront()) JS::TraceEdge(trc, &e.mutableFront(), "DOM expando object"); } } } static void SuspectDOMExpandos(JSObject* obj, nsCycleCollectionNoteRootCallback& cb) { MOZ_ASSERT(dom::GetDOMClass(obj) && dom::GetDOMClass(obj)->mDOMObjectIsISupports); nsISupports* native = dom::UnwrapDOMObject(obj); cb.NoteXPCOMRoot(native); } // static void XPCWrappedNativeScope::SuspectAllWrappers(XPCJSContext* cx, nsCycleCollectionNoteRootCallback& cb) { for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) { for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) { static_cast(i.Get())->value->Suspect(cb); } if (cur->mDOMExpandoSet) { for (DOMExpandoSet::Range r = cur->mDOMExpandoSet->all(); !r.empty(); r.popFront()) SuspectDOMExpandos(r.front().unbarrieredGet(), cb); } } } // static void XPCWrappedNativeScope::UpdateWeakPointersAfterGC(XPCJSContext* cx) { // If this is called from the finalization callback in JSGC_MARK_END then // JSGC_FINALIZE_END must always follow it calling // FinishedFinalizationPhaseOfGC and clearing gDyingScopes in // KillDyingScopes. MOZ_ASSERT(!gDyingScopes, "JSGC_MARK_END without JSGC_FINALIZE_END"); XPCWrappedNativeScope* prev = nullptr; XPCWrappedNativeScope* cur = gScopes; while (cur) { // Sweep waivers. if (cur->mWaiverWrapperMap) cur->mWaiverWrapperMap->Sweep(); XPCWrappedNativeScope* next = cur->mNext; if (cur->mContentXBLScope) cur->mContentXBLScope.updateWeakPointerAfterGC(); for (size_t i = 0; i < cur->mAddonScopes.Length(); i++) cur->mAddonScopes[i].updateWeakPointerAfterGC(); // Check for finalization of the global object or update our pointer if // it was moved. if (cur->mGlobalJSObject) { cur->mGlobalJSObject.updateWeakPointerAfterGC(); if (!cur->mGlobalJSObject) { // Move this scope from the live list to the dying list. if (prev) prev->mNext = next; else gScopes = next; cur->mNext = gDyingScopes; gDyingScopes = cur; cur = nullptr; } } if (cur) prev = cur; cur = next; } } // static void XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs() { for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) { for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) { auto entry = static_cast(i.Get()); entry->value->SweepTearOffs(); } } } // static void XPCWrappedNativeScope::KillDyingScopes() { XPCWrappedNativeScope* cur = gDyingScopes; while (cur) { XPCWrappedNativeScope* next = cur->mNext; if (cur->mGlobalJSObject) CompartmentPrivate::Get(cur->mGlobalJSObject)->scope = nullptr; delete cur; cur = next; } gDyingScopes = nullptr; } //static void XPCWrappedNativeScope::SystemIsBeingShutDown() { int liveScopeCount = 0; XPCWrappedNativeScope* cur; // First move all the scopes to the dying list. cur = gScopes; while (cur) { XPCWrappedNativeScope* next = cur->mNext; cur->mNext = gDyingScopes; gDyingScopes = cur; cur = next; liveScopeCount++; } gScopes = nullptr; // We're forcibly killing scopes, rather than allowing them to go away // when they're ready. As such, we need to do some cleanup before they // can safely be destroyed. for (cur = gDyingScopes; cur; cur = cur->mNext) { // Give the Components object a chance to try to clean up. if (cur->mComponents) cur->mComponents->SystemIsBeingShutDown(); // Walk the protos first. Wrapper shutdown can leave dangling // proto pointers in the proto map. for (auto i = cur->mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) { auto entry = static_cast(i.Get()); entry->value->SystemIsBeingShutDown(); i.Remove(); } for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) { auto entry = static_cast(i.Get()); XPCWrappedNative* wrapper = entry->value; if (wrapper->IsValid()) { wrapper->SystemIsBeingShutDown(); } i.Remove(); } } // Now it is safe to kill all the scopes. KillDyingScopes(); } /***************************************************************************/ JSObject* XPCWrappedNativeScope::GetExpandoChain(HandleObject target) { MOZ_ASSERT(ObjectScope(target) == this); if (!mXrayExpandos.initialized()) return nullptr; return mXrayExpandos.lookup(target); } bool XPCWrappedNativeScope::SetExpandoChain(JSContext* cx, HandleObject target, HandleObject chain) { MOZ_ASSERT(ObjectScope(target) == this); MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx)); MOZ_ASSERT_IF(chain, ObjectScope(chain) == this); if (!mXrayExpandos.initialized() && !mXrayExpandos.init(cx)) return false; return mXrayExpandos.put(cx, target, chain); } /* static */ bool XPCWrappedNativeScope::SetAddonInterposition(JSContext* cx, JSAddonId* addonId, nsIAddonInterposition* interp) { if (!gInterpositionMap) { gInterpositionMap = new InterpositionMap(); bool ok = gInterpositionMap->init(); NS_ENSURE_TRUE(ok, false); if (!gShutdownObserverInitialized) { gShutdownObserverInitialized = true; nsContentUtils::RegisterShutdownObserver(new ClearInterpositionsObserver()); } } if (interp) { bool ok = gInterpositionMap->put(addonId, interp); NS_ENSURE_TRUE(ok, false); UpdateInterpositionWhitelist(cx, interp); } else { gInterpositionMap->remove(addonId); } return true; } /* static */ bool XPCWrappedNativeScope::AllowCPOWsInAddon(JSContext* cx, JSAddonId* addonId, bool allow) { if (!gAllowCPOWAddonSet) { gAllowCPOWAddonSet = new AddonSet(); bool ok = gAllowCPOWAddonSet->init(); NS_ENSURE_TRUE(ok, false); if (!gShutdownObserverInitialized) { gShutdownObserverInitialized = true; nsContentUtils::RegisterShutdownObserver(new ClearInterpositionsObserver()); } } if (allow) { bool ok = gAllowCPOWAddonSet->put(addonId); NS_ENSURE_TRUE(ok, false); } else { gAllowCPOWAddonSet->remove(addonId); } return true; } nsCOMPtr XPCWrappedNativeScope::GetInterposition() { return mInterposition; } /* static */ InterpositionWhitelist* XPCWrappedNativeScope::GetInterpositionWhitelist(nsIAddonInterposition* interposition) { if (!gInterpositionWhitelists) return nullptr; InterpositionWhitelistArray& wls = *gInterpositionWhitelists; for (size_t i = 0; i < wls.Length(); i++) { if (wls[i].interposition == interposition) return &wls[i].whitelist; } return nullptr; } /* static */ bool XPCWrappedNativeScope::UpdateInterpositionWhitelist(JSContext* cx, nsIAddonInterposition* interposition) { // We want to set the interpostion whitelist only once. InterpositionWhitelist* whitelist = GetInterpositionWhitelist(interposition); if (whitelist) return true; // The hashsets in gInterpositionWhitelists do not have a copy constructor so // a reallocation for the array will lead to a memory corruption. If you // need more interpositions, change the capacity of the array please. static const size_t MAX_INTERPOSITION = 8; if (!gInterpositionWhitelists) gInterpositionWhitelists = new InterpositionWhitelistArray(MAX_INTERPOSITION); MOZ_RELEASE_ASSERT(MAX_INTERPOSITION > gInterpositionWhitelists->Length() + 1); InterpositionWhitelistPair* newPair = gInterpositionWhitelists->AppendElement(); newPair->interposition = interposition; if (!newPair->whitelist.init()) { JS_ReportOutOfMemory(cx); return false; } whitelist = &newPair->whitelist; RootedValue whitelistVal(cx); nsresult rv = interposition->GetWhitelist(&whitelistVal); if (NS_FAILED(rv)) { JS_ReportErrorASCII(cx, "Could not get the whitelist from the interposition."); return false; } if (!whitelistVal.isObject()) { JS_ReportErrorASCII(cx, "Whitelist must be an array."); return false; } // We want to enter the whitelist's compartment to avoid any wrappers. // To be on the safe side let's make sure that it's a system compartment // and we don't accidentally trigger some content function here by parsing // the whitelist object. RootedObject whitelistObj(cx, &whitelistVal.toObject()); whitelistObj = js::UncheckedUnwrap(whitelistObj); if (!AccessCheck::isChrome(whitelistObj)) { JS_ReportErrorASCII(cx, "Whitelist must be from system scope."); return false; } { JSAutoCompartment ac(cx, whitelistObj); bool isArray; if (!JS_IsArrayObject(cx, whitelistObj, &isArray)) return false; if (!isArray) { JS_ReportErrorASCII(cx, "Whitelist must be an array."); return false; } uint32_t length; if (!JS_GetArrayLength(cx, whitelistObj, &length)) return false; for (uint32_t i = 0; i < length; i++) { RootedValue idval(cx); if (!JS_GetElement(cx, whitelistObj, i, &idval)) return false; if (!idval.isString()) { JS_ReportErrorASCII(cx, "Whitelist must contain strings only."); return false; } RootedString str(cx, idval.toString()); str = JS_AtomizeAndPinJSString(cx, str); if (!str) { JS_ReportErrorASCII(cx, "String internization failed."); return false; } // By internizing the id's we ensure that they won't get // GCed so we can use them as hash keys. jsid id = INTERNED_STRING_TO_JSID(cx, str); if (!whitelist->put(JSID_BITS(id))) { JS_ReportOutOfMemory(cx); return false; } } } return true; } /***************************************************************************/ // static void XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth) { #ifdef DEBUG depth-- ; // get scope count. int count = 0; XPCWrappedNativeScope* cur; for (cur = gScopes; cur; cur = cur->mNext) count++ ; XPC_LOG_ALWAYS(("chain of %d XPCWrappedNativeScope(s)", count)); XPC_LOG_INDENT(); XPC_LOG_ALWAYS(("gDyingScopes @ %x", gDyingScopes)); if (depth) for (cur = gScopes; cur; cur = cur->mNext) cur->DebugDump(depth); XPC_LOG_OUTDENT(); #endif } void XPCWrappedNativeScope::DebugDump(int16_t depth) { #ifdef DEBUG depth-- ; XPC_LOG_ALWAYS(("XPCWrappedNativeScope @ %x", this)); XPC_LOG_INDENT(); XPC_LOG_ALWAYS(("mNext @ %x", mNext)); XPC_LOG_ALWAYS(("mComponents @ %x", mComponents.get())); XPC_LOG_ALWAYS(("mGlobalJSObject @ %x", mGlobalJSObject.get())); XPC_LOG_ALWAYS(("mWrappedNativeMap @ %x with %d wrappers(s)", mWrappedNativeMap, mWrappedNativeMap->Count())); // iterate contexts... if (depth && mWrappedNativeMap->Count()) { XPC_LOG_INDENT(); for (auto i = mWrappedNativeMap->Iter(); !i.Done(); i.Next()) { auto entry = static_cast(i.Get()); entry->value->DebugDump(depth); } XPC_LOG_OUTDENT(); } XPC_LOG_ALWAYS(("mWrappedNativeProtoMap @ %x with %d protos(s)", mWrappedNativeProtoMap, mWrappedNativeProtoMap->Count())); // iterate contexts... if (depth && mWrappedNativeProtoMap->Count()) { XPC_LOG_INDENT(); for (auto i = mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) { auto entry = static_cast(i.Get()); entry->value->DebugDump(depth); } XPC_LOG_OUTDENT(); } XPC_LOG_OUTDENT(); #endif } void XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(ScopeSizeInfo* scopeSizeInfo) { for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) cur->AddSizeOfIncludingThis(scopeSizeInfo); } void XPCWrappedNativeScope::AddSizeOfIncludingThis(ScopeSizeInfo* scopeSizeInfo) { scopeSizeInfo->mScopeAndMapSize += scopeSizeInfo->mMallocSizeOf(this); scopeSizeInfo->mScopeAndMapSize += mWrappedNativeMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); scopeSizeInfo->mScopeAndMapSize += mWrappedNativeProtoMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); if (dom::HasProtoAndIfaceCache(mGlobalJSObject)) { dom::ProtoAndIfaceCache* cache = dom::GetProtoAndIfaceCache(mGlobalJSObject); scopeSizeInfo->mProtoAndIfaceCacheSize += cache->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); } // There are other XPCWrappedNativeScope members that could be measured; // the above ones have been seen by DMD to be worth measuring. More stuff // may be added later. }