summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src/XPCWrappedNativeScope.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/src/XPCWrappedNativeScope.cpp')
-rw-r--r--js/xpconnect/src/XPCWrappedNativeScope.cpp934
1 files changed, 934 insertions, 0 deletions
diff --git a/js/xpconnect/src/XPCWrappedNativeScope.cpp b/js/xpconnect/src/XPCWrappedNativeScope.cpp
new file mode 100644
index 000000000..24f1067d0
--- /dev/null
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -0,0 +1,934 @@
+/* -*- 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<const js::Class*> 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<nsIXPCComponents> 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<nsIXPCComponents> 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
+ // <destructor> 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<nsCOMPtr<nsIPrincipal>> principalAsArray(1);
+ principalAsArray.AppendElement(principal);
+ nsCOMPtr<nsIExpandedPrincipal> 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<Native2WrappedNativeMap::Entry*>(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<nsISupports>(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<Native2WrappedNativeMap::Entry*>(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<Native2WrappedNativeMap::Entry*>(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<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
+ entry->value->SystemIsBeingShutDown();
+ i.Remove();
+ }
+ for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
+ auto entry = static_cast<Native2WrappedNativeMap::Entry*>(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<nsIAddonInterposition>
+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<Native2WrappedNativeMap::Entry*>(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<ClassInfo2WrappedNativeProtoMap::Entry*>(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.
+}