Bug 821850 - Add infrastructure for lazily-created XBL scopes. r=bz
authorBobby Holley <bobbyholley@gmail.com>
Fri, 08 Feb 2013 14:24:19 +0000
changeset 131181 580a65d9eb1e5069409812a6693e23e586299253
parent 131180 cc884677ee404a23298109794843bf2c71ec8cd3
child 131182 f168a07ce06f1c962b6afd8116ca103ce3adf4ec
push id2323
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 19:47:02 +0000
treeherdermozilla-beta@7712be144d91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs821850
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 821850 - Add infrastructure for lazily-created XBL scopes. r=bz
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/XPCWrappedNativeScope.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/src/xpcpublic.h
modules/libpref/src/init/all.js
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2335,16 +2335,17 @@ CompartmentNameCallback(JSRuntime *rt, J
     nsCString name;
     GetCompartmentName(comp, name, false);
     if (name.Length() >= bufsize)
         name.Truncate(bufsize - 1);
     memcpy(buf, name.get(), name.Length() + 1);
 }
 
 bool XPCJSRuntime::gExperimentalBindingsEnabled;
+bool XPCJSRuntime::gXBLScopesEnabled;
 
 static bool
 PreserveWrapper(JSContext *cx, JSObject *obj)
 {
     MOZ_ASSERT(cx);
     MOZ_ASSERT(obj);
     MOZ_ASSERT(js::GetObjectClass(obj)->ext.isWrappedNative ||
                mozilla::dom::IsDOMObject(obj));
@@ -2514,16 +2515,19 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
         JS_NewDHashTable(JS_DHashGetStubOps(), nullptr,
                          sizeof(JSDHashEntryStub), 128);
 #endif
 
     DOM_InitInterfaces();
     Preferences::AddBoolVarCache(&gExperimentalBindingsEnabled,
                                  "dom.experimental_bindings",
                                  false);
+    Preferences::AddBoolVarCache(&gXBLScopesEnabled,
+                                 "dom.xbl_scopes",
+                                 false);
 
 
     // these jsids filled in later when we have a JSContext to work with.
     mStrIDs[0] = JSID_VOID;
 
     mJSRuntime = JS_NewRuntime(32L * 1024L * 1024L, JS_USE_HELPER_THREADS); // pref ?
     if (!mJSRuntime)
         NS_RUNTIMEABORT("JS_NewRuntime failed.");
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -3,16 +3,18 @@
  * 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 "jsproxy.h"
+#include "nsContentUtils.h"
+#include "nsPrincipal.h"
 
 #include "mozilla/dom/BindingUtils.h"
 
 using namespace mozilla;
 using namespace xpc;
 
 /***************************************************************************/
 
@@ -101,17 +103,18 @@ XPCWrappedNativeScope::XPCWrappedNativeS
                                              JSObject* aGlobal)
       : mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_SIZE)),
         mWrappedNativeProtoMap(ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_SIZE)),
         mMainThreadWrappedNativeProtoMap(ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_SIZE)),
         mComponents(nullptr),
         mNext(nullptr),
         mGlobalJSObject(nullptr),
         mPrototypeNoHelper(nullptr),
-        mExperimentalBindingsEnabled(XPCJSRuntime::Get()->ExperimentalBindingsEnabled())
+        mExperimentalBindingsEnabled(XPCJSRuntime::Get()->ExperimentalBindingsEnabled()),
+        mIsXBLScope(false)
 {
     // add ourselves to the scopes list
     {   // scoped lock
         XPCAutoLock lock(XPCJSRuntime::Get()->GetMapLock());
 
 #ifdef DEBUG
         for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext)
             MOZ_ASSERT(aGlobal != cur->GetGlobalJSObjectPreserveColor(), "dup object");
@@ -168,16 +171,92 @@ XPCWrappedNativeScope::GetComponentsJSOb
     // The call to wrap() here is necessary even though the object is same-
     // compartment, because it applies our security wrapper.
     JSObject *obj = wrapper->GetFlatJSObject();
     if (!JS_WrapObject(ccx, &obj))
         return nullptr;
     return obj;
 }
 
+JSObject*
+XPCWrappedNativeScope::EnsureXBLScope(JSContext *cx)
+{
+    JSObject *global = GetGlobalJSObject();
+    MOZ_ASSERT(js::IsObjectInContextCompartment(global, cx));
+    MOZ_ASSERT(!mIsXBLScope);
+    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 (mXBLScope)
+        return mXBLScope;
+
+    // We should only be applying XBL bindings in DOM scopes.
+    MOZ_ASSERT(!strcmp(js::GetObjectClass(mGlobalJSObject)->name, "Window") ||
+               !strcmp(js::GetObjectClass(mGlobalJSObject)->name, "ChromeWindow") ||
+               !strcmp(js::GetObjectClass(mGlobalJSObject)->name, "ModalContentWindow"));
+
+    // Get the scope principal. If it's system, there's no reason to make
+    // a separate XBL scope.
+    nsIPrincipal *principal = GetPrincipal();
+    if (!principal)
+        return nullptr;
+    if (nsContentUtils::IsSystemPrincipal(principal))
+        return global;
+
+    // Check the pref. If XBL scopes are disabled, just return the global.
+    if (!XPCJSRuntime::Get()->XBLScopesEnabled())
+        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.
+    //
+    // NB: One would think that wantXrays wouldn't make a difference here.
+    // However, wantXrays lives a secret double life, and one of its other
+    // hobbies is to waive Xray on the returned sandbox when set to false.
+    // So make sure to keep this set to true, here.
+    SandboxOptions options;
+    options.wantXrays = true;
+    options.wantComponents = true;
+    options.wantXHRConstructor = false;
+    options.proto = global;
+
+    // Use an nsExpandedPrincipal to create asymmetric security.
+    nsCOMPtr<nsIExpandedPrincipal> ep;
+    MOZ_ASSERT(!(ep = do_QueryInterface(principal)));
+    nsTArray< nsCOMPtr<nsIPrincipal> > principalAsArray(1);
+    principalAsArray.AppendElement(principal);
+    ep = new nsExpandedPrincipal(principalAsArray);
+
+    // Create the sandbox.
+    JSAutoRequest ar(cx);
+    JS::Value v = JS::UndefinedValue();
+    nsresult rv = xpc_CreateSandboxObject(cx, &v, ep, options);
+    NS_ENSURE_SUCCESS(rv, nullptr);
+    mXBLScope = &v.toObject();
+
+    // Tag it.
+    EnsureCompartmentPrivate(js::UnwrapObject(mXBLScope))->scope->mIsXBLScope = true;
+
+    // Good to go!
+    return mXBLScope;
+}
+
+namespace xpc {
+JSObject *GetXBLScope(JSContext *cx, JSObject *contentScope)
+{
+    JSAutoCompartment ac(cx, contentScope);
+    JSObject *scope = EnsureCompartmentPrivate(contentScope)->scope->EnsureXBLScope(cx);
+    scope = js::UnwrapObject(scope);
+    xpc_UnmarkGrayObject(scope);
+    return scope;
+}
+} /* namespace xpc */
+
 // Dummy JS class to let wrappers w/o an xpc prototype share
 // scopes. By doing this we avoid allocating a new scope for every
 // wrapper on creation of the wrapper, and most wrappers won't need
 // their own scope at all for the lifetime of the wrapper.
 // WRAPPER_SLOTS is key here (even though there's never anything
 // in the private data slot in these prototypes), as the number of
 // reserved slots in this class needs to match that of the wrappers
 // for the JS engine to share scopes.
@@ -250,16 +329,17 @@ XPCWrappedNativeScope::~XPCWrappedNative
     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;
 
     JSRuntime *rt = XPCJSRuntime::Get()->GetJSRuntime();
+    mXBLScope.finalize(rt);
     mGlobalJSObject.finalize(rt);
 }
 
 JSObject *
 XPCWrappedNativeScope::GetPrototypeNoHelper(XPCCallContext& ccx)
 {
     // We could create this prototype in SetGlobal(), but all scopes
     // don't need one, so we save ourselves a bit of space if we
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -907,32 +907,37 @@ public:
 
     static void ActivityCallback(void *arg, JSBool active);
 
     bool ExperimentalBindingsEnabled()
     {
         return gExperimentalBindingsEnabled;
     }
 
+    bool XBLScopesEnabled() {
+        return gXBLScopesEnabled;
+    }
+
     size_t SizeOfIncludingThis(nsMallocSizeOfFun mallocSizeOf);
 
     AutoMarkingPtr**  GetAutoRootsAdr() {return &mAutoRoots;}
 
 private:
     XPCJSRuntime(); // no implementation
     XPCJSRuntime(nsXPConnect* aXPConnect);
 
     // The caller must be holding the GC lock
     void RescheduleWatchdog(XPCContext* ccx);
 
     static void WatchdogMain(void *arg);
 
     void ReleaseIncrementally(nsTArray<nsISupports *> &array);
 
     static bool gExperimentalBindingsEnabled;
+    static bool gXBLScopesEnabled;
 
     static const char* mStrings[IDX_TOTAL_COUNT];
     jsid mStrIDs[IDX_TOTAL_COUNT];
     jsval mStrJSVals[IDX_TOTAL_COUNT];
 
     nsXPConnect*             mXPConnect;
     JSRuntime*               mJSRuntime;
     XPCJSContextStack*       mJSContextStack;
@@ -1643,16 +1648,18 @@ public:
 
     static void
     TraceWrappedNativesInAllScopes(JSTracer* trc, XPCJSRuntime* rt);
 
     void TraceSelf(JSTracer *trc) {
         JSObject *obj = GetGlobalJSObjectPreserveColor();
         MOZ_ASSERT(obj);
         JS_CALL_OBJECT_TRACER(trc, obj, "XPCWrappedNativeScope::mGlobalJSObject");
+        if (mXBLScope)
+            JS_CALL_OBJECT_TRACER(trc, mXBLScope, "XPCWrappedNativeScope::mXBLScope");
     }
 
     static void
     SuspectAllWrappers(XPCJSRuntime* rt, nsCycleCollectionTraversalCallback &cb);
 
     static void
     StartFinalizationPhaseOfGC(JSFreeOp *fop, XPCJSRuntime* rt);
 
@@ -1712,20 +1719,27 @@ public:
         }
         return mDOMExpandoMap->PutEntry(expando, mozilla::fallible_t());
     }
     void RemoveDOMExpandoObject(JSObject *expando) {
         if (mDOMExpandoMap)
             mDOMExpandoMap->RemoveEntry(expando);
     }
 
+    // Gets the appropriate scope object for XBL in this scope. The context
+    // must be same-compartment with the global upon entering, and the scope
+    // object is wrapped into the compartment of the global.
+    JSObject *EnsureXBLScope(JSContext *cx);
+
     XPCWrappedNativeScope(JSContext *cx, JSObject* aGlobal);
 
     nsAutoPtr<JSObject2JSObjectMap> mWaiverWrapperMap;
 
+    bool IsXBLScope() { return mIsXBLScope; }
+
 protected:
     virtual ~XPCWrappedNativeScope();
 
     static void KillDyingScopes();
 
     XPCWrappedNativeScope(); // not implemented
 
 private:
@@ -1739,24 +1753,30 @@ private:
     nsRefPtr<nsXPCComponents>        mComponents;
     XPCWrappedNativeScope*           mNext;
     // The JS global object for this scope.  If non-null, this will be the
     // default parent for the XPCWrappedNatives that have us as the scope,
     // unless a PreCreate hook overrides it.  Note that this _may_ be null (see
     // constructor).
     js::ObjectPtr                    mGlobalJSObject;
 
+    // XBL Scope. This is is a lazily-created sandbox for non-system scopes.
+    // EnsureXBLScope() decides whether it needs to be created or not.
+    // This reference is wrapped into the compartment of mGlobalJSObject.
+    js::ObjectPtr                    mXBLScope;
+
     // Prototype to use for wrappers with no helper.
     JSObject*                        mPrototypeNoHelper;
 
     XPCContext*                      mContext;
 
     nsAutoPtr<DOMExpandoMap> mDOMExpandoMap;
 
     JSBool mExperimentalBindingsEnabled;
+    bool mIsXBLScope;
 };
 
 /***************************************************************************/
 // XPCNativeMember represents a single idl declared method, attribute or
 // constant.
 
 // Tight. No virtual methods. Can be bitwise copied (until any resolution done).
 
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -38,16 +38,25 @@ class nsScriptNameSpaceManager;
 namespace xpc {
 JSObject *
 TransplantObject(JSContext *cx, JSObject *origobj, JSObject *target);
 
 JSObject *
 TransplantObjectWithWrapper(JSContext *cx,
                             JSObject *origobj, JSObject *origwrapper,
                             JSObject *targetobj, JSObject *targetwrapper);
+
+// Return a raw XBL scope object corresponding to contentScope, which must
+// be an object whose global is a DOM window.
+//
+// The return value is not wrapped into cx->compartment, so be sure to enter
+// its compartment before doing anything meaningful.
+JSObject *
+GetXBLScope(JSContext *cx, JSObject *contentScope);
+
 } /* namespace xpc */
 
 #define XPCONNECT_GLOBAL_FLAGS                                                \
     JSCLASS_DOM_GLOBAL | JSCLASS_HAS_PRIVATE |                                \
     JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_IMPLEMENTS_BARRIERS |            \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(2)
 
 void
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -701,16 +701,19 @@ pref("dom.send_after_paint_to_content", 
 pref("dom.min_timeout_value", 4);
 // And for background windows
 pref("dom.min_background_timeout_value", 1000);
 
 // Use the new DOM bindings (only affects any scopes created after the pref is
 // changed)
 pref("dom.experimental_bindings", true);
 
+// Run content XBL in a separate scope.
+pref("dom.xbl_scopes", false);
+
 // Don't use new input types
 pref("dom.experimental_forms", false);
 
 // Allocation Threshold for Workers
 pref("dom.workers.mem.gc_allocation_threshold_mb", 30);
 
 // Parsing perf prefs. For now just mimic what the old code did.
 #ifndef XP_WIN