diff --git a/content/xul/document/src/XULDocument.cpp b/content/xul/document/src/XULDocument.cpp index deb00497bd3..bcab62d4831 100644 --- a/content/xul/document/src/XULDocument.cpp +++ b/content/xul/document/src/XULDocument.cpp @@ -82,6 +82,7 @@ #include "nsXULPopupManager.h" #include "nsCCUncollectableMarker.h" #include "nsURILoader.h" +#include "mozilla/AddonPathService.h" #include "mozilla/BasicEvents.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/NodeInfoInlines.h" @@ -3691,9 +3692,14 @@ XULDocument::ExecuteScript(nsIScriptContext * aContext, nsAutoMicroTask mt; JSContext *cx = aContext->GetNativeContext(); AutoCxPusher pusher(cx); - JS::Rooted global(cx, mScriptGlobalObject->GetGlobalJSObject()); + JS::Rooted baseGlobal(cx, mScriptGlobalObject->GetGlobalJSObject()); + NS_ENSURE_TRUE(baseGlobal, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(nsContentUtils::GetSecurityManager()->ScriptAllowed(baseGlobal), NS_OK); + + JSAddonId *addonId = MapURIToAddonID(mCurrentPrototype->GetURI()); + JS::Rooted global(cx, xpc::GetAddonScope(cx, baseGlobal, addonId)); NS_ENSURE_TRUE(global, NS_ERROR_FAILURE); - NS_ENSURE_TRUE(nsContentUtils::GetSecurityManager()->ScriptAllowed(global), NS_OK); + JS::ExposeObjectToActiveJS(global); xpc_UnmarkGrayScript(aScriptObject); JSAutoCompartment ac(cx, global); diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index 71955605105..5b135b2b1d2 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -6,6 +6,7 @@ // Microsoft's API Name hackery sucks #undef CreateEvent +#include "mozilla/AddonPathService.h" #include "mozilla/BasicEvents.h" #include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/EventDispatcher.h" @@ -842,6 +843,8 @@ EventListenerManager::CompileEventHandlerInternal(Listener* aListener, typeAtom, win, &argCount, &argNames); + JSAddonId *addonId = MapURIToAddonID(uri); + // Wrap the event target, so that we can use it as the scope for the event // handler. Note that mTarget is different from aElement in the case, // where mTarget is a Window. @@ -858,6 +861,17 @@ EventListenerManager::CompileEventHandlerInternal(Listener* aListener, return rv; } } + if (addonId) { + JS::Rooted vObj(cx, &v.toObject()); + JS::Rooted addonScope(cx, xpc::GetAddonScope(cx, vObj, addonId)); + if (!addonScope) { + return NS_ERROR_FAILURE; + } + JSAutoCompartment ac(cx, addonScope); + if (!JS_WrapValue(cx, &v)) { + return NS_ERROR_FAILURE; + } + } JS::Rooted target(cx, &v.toObject()); JSAutoCompartment ac(cx, target); diff --git a/js/xpconnect/loader/mozJSComponentLoader.cpp b/js/xpconnect/loader/mozJSComponentLoader.cpp index 0da177a2df3..f8e9af74411 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.cpp +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp @@ -44,6 +44,7 @@ #include "nsCxPusher.h" #include "WrapperFactory.h" +#include "mozilla/AddonPathService.h" #include "mozilla/scache/StartupCache.h" #include "mozilla/scache/StartupCacheUtils.h" #include "mozilla/MacroForEach.h" @@ -682,7 +683,9 @@ mozJSComponentLoader::PrepareObjectForLocation(JSCLContextHelper& aCx, CompartmentOptions options; options.setZone(SystemZone) - .setVersion(JSVERSION_LATEST); + .setVersion(JSVERSION_LATEST) + .setAddonId(aReuseLoaderGlobal ? nullptr : MapURIToAddonID(aURI)); + // Defer firing OnNewGlobalObject until after the __URI__ property has // been defined so the JS debugger can tell what module the global is // for diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 8ffff4ce693..7483f086307 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -522,6 +522,14 @@ IsInContentXBLScope(JSObject *obj) return IsContentXBLScope(js::GetObjectCompartment(obj)); } +bool +IsInAddonScope(JSObject *obj) +{ + // We always eagerly create compartment privates for addon scopes. + XPCWrappedNativeScope *scope = GetObjectScope(obj); + return scope && scope->IsAddonScope(); +} + bool IsUniversalXPConnectEnabled(JSCompartment *compartment) { diff --git a/js/xpconnect/src/XPCWrappedNativeScope.cpp b/js/xpconnect/src/XPCWrappedNativeScope.cpp index c15cb442e47..8978a777775 100644 --- a/js/xpconnect/src/XPCWrappedNativeScope.cpp +++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp @@ -73,7 +73,8 @@ XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext *cx, mComponents(nullptr), mNext(nullptr), mGlobalJSObject(aGlobal), - mIsContentXBLScope(false) + mIsContentXBLScope(false), + mIsAddonScope(false) { // add ourselves to the scopes list { @@ -185,6 +186,20 @@ XPCWrappedNativeScope::AttachComponentsObject(JSContext* aCx) JSPROP_PERMANENT | JSPROP_READONLY); } +static bool +CompartmentPerAddon() +{ + static bool initialized = false; + static bool pref = false; + + if (!initialized) { + pref = Preferences::GetBool("dom.compartment_per_addon", false); + initialized = true; + } + + return pref; +} + JSObject* XPCWrappedNativeScope::EnsureContentXBLScope(JSContext *cx) { @@ -249,6 +264,8 @@ XPCWrappedNativeScope::AllowContentXBLScope() namespace xpc { JSObject *GetXBLScope(JSContext *cx, JSObject *contentScopeArg) { + MOZ_ASSERT(!IsInAddonScope(contentScopeArg)); + JS::RootedObject contentScope(cx, contentScopeArg); JSAutoCompartment ac(cx, contentScope); JSObject *scope = EnsureCompartmentPrivate(contentScope)->scope->EnsureContentXBLScope(cx); @@ -274,6 +291,57 @@ UseContentXBLScope(JSCompartment *c) } /* 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())); + + // 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()); + + EnsureCompartmentPrivate(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 = EnsureCompartmentPrivate(contentScope)->scope; + 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); @@ -304,6 +372,8 @@ XPCWrappedNativeScope::~XPCWrappedNativeScope() JSRuntime *rt = XPCJSRuntime::Get()->Runtime(); mContentXBLScope.finalize(rt); + for (size_t i = 0; i < mAddonScopes.Length(); i++) + mAddonScopes[i].finalize(rt); mGlobalJSObject.finalize(rt); } diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index aaf1baaa07f..2900a9c0dd7 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -1024,6 +1024,8 @@ public: mGlobalJSObject.trace(trc, "XPCWrappedNativeScope::mGlobalJSObject"); if (mContentXBLScope) mContentXBLScope.trace(trc, "XPCWrappedNativeScope::mXBLScope"); + for (size_t i = 0; i < mAddonScopes.Length(); i++) + mAddonScopes[i].trace(trc, "XPCWrappedNativeScope::mAddonScopes"); if (mXrayExpandos.initialized()) mXrayExpandos.trace(trc); } @@ -1101,6 +1103,8 @@ public: // object is wrapped into the compartment of the global. JSObject *EnsureContentXBLScope(JSContext *cx); + JSObject *EnsureAddonScope(JSContext *cx, JSAddonId *addonId); + XPCWrappedNativeScope(JSContext *cx, JS::HandleObject aGlobal); nsAutoPtr mWaiverWrapperMap; @@ -1109,6 +1113,8 @@ public: bool AllowContentXBLScope(); bool UseContentXBLScope() { return mUseContentXBLScope; } + bool IsAddonScope() { return mIsAddonScope; } + protected: virtual ~XPCWrappedNativeScope(); @@ -1136,11 +1142,15 @@ private: // This reference is wrapped into the compartment of mGlobalJSObject. JS::ObjectPtr mContentXBLScope; + // Lazily created sandboxes for addon code. + nsTArray mAddonScopes; + nsAutoPtr mDOMExpandoSet; JS::WeakMapPtr mXrayExpandos; bool mIsContentXBLScope; + bool mIsAddonScope; // For remote XUL domains, we run all XBL in the content scope for compat // reasons (though we sometimes pref this off for automation). We separately diff --git a/js/xpconnect/src/xpcpublic.h b/js/xpconnect/src/xpcpublic.h index fb2e8a9bf59..2d240ebd13f 100644 --- a/js/xpconnect/src/xpcpublic.h +++ b/js/xpconnect/src/xpcpublic.h @@ -109,6 +109,12 @@ AllowContentXBLScope(JSCompartment *c); bool UseContentXBLScope(JSCompartment *c); +bool +IsInAddonScope(JSObject *obj); + +JSObject * +GetAddonScope(JSContext *cx, JS::HandleObject contentScope, JSAddonId *addonId); + bool IsSandboxPrototypeProxy(JSObject *obj); diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index aa0d9085ac4..84ae11b2fda 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -138,6 +138,11 @@ pref("dom.webcrypto.enabled", true); // Whether the UndoManager API is enabled pref("dom.undo_manager.enabled", false); +// Whether to run add-on code in different compartments from browser code. This +// causes a separate compartment for each (addon, global) combination, which may +// significantly increase the number of compartments in the system. +pref("dom.compartment_per_addon", false); + // Fastback caching - if this pref is negative, then we calculate the number // of content viewers to cache based on the amount of available memory. pref("browser.sessionhistory.max_total_viewers", -1);