Bug 1431255 - Create per-origin sandboxes from XPCJSRuntime and load UA widgets scripts draft
authorTimothy Guan-tin Chien <timdream@gmail.com>
Wed, 27 Jun 2018 11:34:07 -0700
changeset 813862 b7cc1d767bd2b4b9464c2f1d3e931b2c9834da58
parent 813861 7fe9d213f148bba53879f16be330d732e724a5a9
child 813863 4ae95a166a6b064fb5de58bd2f73ef99c777edd2
push id115029
push usertimdream@gmail.com
push dateWed, 04 Jul 2018 00:11:12 +0000
bugs1431255
milestone63.0a1
Bug 1431255 - Create per-origin sandboxes from XPCJSRuntime and load UA widgets scripts This patch creates the basic structure on how the widget scripts can be loaded and be pointed to the Shadow Root, from tab-content.js. This should be moved to another JSM so we could reuse it on Fennec. The UAWidgets class asks for a sandbox from Cu.getUAWidgetScope(), which calls into XPCJSRuntime::GetUAWidgetScope(). It creates and keeps the sandboxes, in a GCHashMap keyed to the origin, so we could reuse it if needed. MozReview-Commit-ID: J6W4PDQWMcN
browser/base/content/tab-content.js
caps/BasePrincipal.h
js/xpconnect/idl/xpccomponents.idl
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -493,8 +493,74 @@ addEventListener("MozAfterPaint", functi
 
 // Remove this once bug 1397365 is fixed.
 addEventListener("MozAfterPaint", function onFirstNonBlankPaint() {
   if (content.document.documentURI == "about:blank" && !content.opener)
     return;
   removeEventListener("MozAfterPaint", onFirstNonBlankPaint);
   sendAsyncMessage("Browser:FirstNonBlankPaint");
 });
+
+class UAWidgets {
+  constructor() {
+    addEventListener("UAWidgetBindToTree", this);
+    addEventListener("UAWidgetUnbindFromTree", this);
+
+    this.widgets = new WeakMap();
+  }
+
+  handleEvent(aEvent) {
+    switch (aEvent.type) {
+      case "UAWidgetBindToTree":
+        this.setupWidget(aEvent.target);
+        break;
+      case "UAWidgetUnbindFromTree":
+        this.teardownWidget(aEvent.target);
+        break;
+    }
+  }
+
+  setupWidget(aElement) {
+    let shadowRoot = aElement.openOrClosedShadowRoot;
+    let sandbox = Cu.getUAWidgetScope(aElement);
+
+    let uri;
+    let widgetName;
+    switch (aElement.localName) {
+      case "video":
+      case "audio":
+        // TODO (videocontrols)
+        break;
+      case "input":
+        // TODO (datetimebox)
+        break;
+      case "applet":
+      case "embed":
+      case "object":
+        // TODO (pluginProblems)
+        break;
+    }
+
+    if (!uri || !widgetName) {
+      return;
+    }
+
+    if (!sandbox[widgetName]) {
+      Services.scriptloader.loadSubScript(uri, sandbox, "UTF-8");
+    }
+
+    let widget = new sandbox[widgetName](shadowRoot);
+    this.widgets.set(aElement, widget);
+  }
+
+  teardownWidget(aElement) {
+    let widget = this.widgets.get(aElement);
+    if (!widget) {
+      return;
+    }
+    if (typeof widget.destructor == "function") {
+      widget.destructor();
+    }
+    this.widgets.delete(aElement);
+  }
+}
+
+new UAWidgets();
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -157,16 +157,18 @@ public:
     }
     // Extension principals always override the CSP non-extension principals.
     // This is primarily for the sake of their stylesheets, which are usually
     // loaded from channels and cannot have expanded principals.
     return (AddonPolicy() &&
             !BasePrincipal::Cast(aDocumentPrincipal)->AddonPolicy());
   }
 
+  uint32_t GetOriginNoSuffixHash() const { return mOriginNoSuffix->hash(); }
+
 protected:
   virtual ~BasePrincipal();
 
   // Note that this does not check OriginAttributes. Callers that depend on
   // those must call Subsumes instead.
   virtual bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsider) = 0;
 
   // Internal, side-effect-free check to determine whether the concrete
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -14,16 +14,17 @@ interface nsIClassInfo;
 interface nsIComponentManager;
 interface nsICycleCollectorListener;
 interface nsIFile;
 interface nsIURI;
 interface nsIJSCID;
 interface nsIJSIID;
 interface nsIPrincipal;
 interface nsIStackFrame;
+webidl Element;
 
 /**
 * interface of Components.interfacesByID
 * (interesting stuff only reflected into JavaScript)
 */
 [scriptable, uuid(f235ef76-9919-478b-aa0f-282d994ddf76)]
 interface nsIXPCComponents_InterfacesByID : nsISupports
 {
@@ -171,16 +172,23 @@ interface nsIXPCComponents_Utils : nsISu
      */
     [implicit_jscontext,optional_argc]
     jsval evalInSandbox(in AString source, in jsval sandbox,
                         [optional] in jsval version,
                         [optional] in AUTF8String filename,
                         [optional] in long lineNo);
 
     /*
+     * Get the sandbox for running JS-implemented UA widgets (video controls etc.),
+     * hosted inside UA-created Shadow DOM.
+     */
+    [implicit_jscontext]
+    jsval getUAWidgetScope(in Element hostElement);
+
+    /*
      * getSandboxMetadata is designed to be called from JavaScript only.
      *
      * getSandboxMetadata retrieves the metadata associated with
      * a sandbox object. It will return undefined if there
      * is no metadata attached to the sandbox.
      *
      * var s = C.u.Sandbox(..., { metadata: "metadata" });
      * var metadata = C.u.getSandboxMetadata(s);
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2162,16 +2162,31 @@ nsXPCComponents_Utils::EvalInSandbox(con
             lineNo = frame->GetLineNumber(cx);
         }
     }
 
     return xpc::EvalInSandbox(cx, sandbox, source, filename, lineNo, retval);
 }
 
 NS_IMETHODIMP
+nsXPCComponents_Utils::GetUAWidgetScope(mozilla::dom::Element* hostElement,
+                                        JSContext* cx,
+                                        MutableHandleValue rval)
+{
+    rval.set(UndefinedValue());
+
+    JSObject* scope = XPCJSRuntime::Get()->GetUAWidgetScope(cx, hostElement->NodePrincipal());
+    NS_ENSURE_TRUE(scope, NS_ERROR_OUT_OF_MEMORY); // See bug 858642.
+
+    rval.set(JS::ObjectValue(*scope));
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsXPCComponents_Utils::GetSandboxMetadata(HandleValue sandboxVal,
                                           JSContext* cx, MutableHandleValue rval)
 {
     if (!sandboxVal.isObject())
         return NS_ERROR_INVALID_ARG;
 
     RootedObject sandbox(cx, &sandboxVal.toObject());
     sandbox = js::CheckedUnwrap(sandbox);
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -869,16 +869,17 @@ XPCJSRuntime::FinalizeCallback(JSFreeOp*
 XPCJSRuntime::WeakPointerZonesCallback(JSContext* cx, void* data)
 {
     // Called before each sweeping slice -- after processing any final marking
     // triggered by barriers -- to clear out any references to things that are
     // about to be finalized and update any pointers to moved GC things.
     XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data);
 
     self->mWrappedJSMap->UpdateWeakPointersAfterGC();
+    self->mUAWidgetScopeMap.sweep();
 
     XPCWrappedNativeScope::UpdateWeakPointersInAllScopesAfterGC();
 }
 
 /* static */ void
 XPCJSRuntime::WeakPointerCompartmentCallback(JSContext* cx, JS::Compartment* comp, void* data)
 {
     // Called immediately after the ZoneGroup weak pointer callback, but only
@@ -2802,16 +2803,17 @@ XPCJSRuntime::XPCJSRuntime(JSContext* aC
    mDyingWrappedNativeProtoMap(XPCWrappedNativeProtoMap::newMap(XPC_DYING_NATIVE_PROTO_MAP_LENGTH)),
    mGCIsRunning(false),
    mNativesToReleaseArray(),
    mDoingFinalization(false),
    mVariantRoots(nullptr),
    mWrappedJSRoots(nullptr),
    mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite())
 {
+    MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.init());
     MOZ_COUNT_CTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
 }
 
 /* static */
 XPCJSRuntime*
 XPCJSRuntime::Get()
 {
     return nsXPConnect::GetRuntimeInstance();
@@ -3050,16 +3052,58 @@ XPCJSRuntime::RemoveGCCallback(xpcGCCall
 {
     MOZ_ASSERT(cb, "null callback");
     bool found = extraGCCallbacks.RemoveElement(cb);
     if (!found) {
         NS_ERROR("Removing a callback which was never added.");
     }
 }
 
+JSObject*
+XPCJSRuntime::GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal)
+{
+    RefPtr<BasePrincipal> key = BasePrincipal::Cast(principal);
+    if (Principal2JSObjectMap::Ptr p = mUAWidgetScopeMap.lookup(key)) {
+        return p->value();
+    }
+
+    // Create a new sandbox
+    JSAutoRequest ar(cx);
+
+    SandboxOptions options;
+    options.sandboxName.AssignLiteral("UA Widget Scope");
+    options.wantXrays = false;
+    options.wantComponents = false;
+
+    MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal));
+    nsTArray<nsCOMPtr<nsIPrincipal>> principalAsArray(1);
+    principalAsArray.AppendElement(principal);
+    RefPtr<ExpandedPrincipal> ep =
+        ExpandedPrincipal::Create(principalAsArray,
+                                  principal->OriginAttributesRef());
+
+    RootedValue v(cx);
+    nsresult rv = CreateSandboxObject(cx, &v,
+                                      static_cast<nsIExpandedPrincipal*>(ep),
+                                      options);
+    NS_ENSURE_SUCCESS(rv, nullptr);
+
+    auto sandbox = &v.toObject();
+
+    RootedObject scope(cx, sandbox);
+    JS::ExposeObjectToActiveJS(scope);
+    if (!JS_WrapObject(cx, &scope)) {
+        return nullptr;
+    }
+
+    MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.putNew(key, scope));
+
+    return scope;
+}
+
 void
 XPCJSRuntime::InitSingletonScopes()
 {
     // This all happens very early, so we don't bother with cx pushing.
     JSContext* cx = XPCJSContext::Get()->Context();
     JSAutoRequest ar(cx);
     RootedValue v(cx);
     nsresult rv;
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -87,16 +87,18 @@
 #include "mozilla/dom/ScriptSettings.h"
 
 #include <math.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include "xpcpublic.h"
+#include "js/HashTable.h"
+#include "js/GCHashTable.h"
 #include "js/TracingAPI.h"
 #include "js/WeakMapPtr.h"
 #include "PLDHashTable.h"
 #include "nscore.h"
 #include "nsXPCOM.h"
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDebug.h"
@@ -567,16 +569,18 @@ public:
 
     bool GCIsRunning() const {return mGCIsRunning;}
 
     ~XPCJSRuntime();
 
     void AddGCCallback(xpcGCCallback cb);
     void RemoveGCCallback(xpcGCCallback cb);
 
+    JSObject* GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal);
+
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
     JSObject* UnprivilegedJunkScope() { return mUnprivilegedJunkScope; }
     JSObject* LoaderGlobal();
 
     void InitSingletonScopes();
     void DeleteSingletonScopes();
 
@@ -590,21 +594,45 @@ private:
     void Shutdown(JSContext* cx) override;
 
     void ReleaseIncrementally(nsTArray<nsISupports*>& array);
 
     static const char* const mStrings[XPCJSContext::IDX_TOTAL_COUNT];
     jsid mStrIDs[XPCJSContext::IDX_TOTAL_COUNT];
     JS::Value mStrJSVals[XPCJSContext::IDX_TOTAL_COUNT];
 
+    struct Hasher {
+        typedef RefPtr<mozilla::BasePrincipal> Key;
+        typedef Key Lookup;
+        static uint32_t hash(const Lookup& l) {
+            return l->GetOriginNoSuffixHash();
+        }
+        static bool match(const Key& k, const Lookup& l) {
+            return k->FastEquals(l);
+        }
+    };
+
+    struct SweepPolicy {
+        static bool needsSweep(RefPtr<mozilla::BasePrincipal>* /* unused */, JS::Heap<JSObject*>* value) {
+            return JS::GCPolicy<JS::Heap<JSObject*>>::needsSweep(value);
+        }
+    };
+
+    typedef JS::GCHashMap<RefPtr<mozilla::BasePrincipal>,
+                          JS::Heap<JSObject*>,
+                          Hasher,
+                          js::SystemAllocPolicy,
+                          SweepPolicy> Principal2JSObjectMap;
+
     JSObject2WrappedJSMap*   mWrappedJSMap;
     IID2WrappedJSClassMap*   mWrappedJSClassMap;
     IID2NativeInterfaceMap*  mIID2NativeInterfaceMap;
     ClassInfo2NativeSetMap*  mClassInfo2NativeSetMap;
     NativeSetMap*            mNativeSetMap;
+    Principal2JSObjectMap    mUAWidgetScopeMap;
     XPCWrappedNativeProtoMap* mDyingWrappedNativeProtoMap;
     bool mGCIsRunning;
     nsTArray<nsISupports*> mNativesToReleaseArray;
     bool mDoingFinalization;
     XPCRootSetElem* mVariantRoots;
     XPCRootSetElem* mWrappedJSRoots;
     nsTArray<xpcGCCallback> extraGCCallbacks;
     JS::GCSliceCallback mPrevGCSliceCallback;