diff --git a/browser/actors/UAWidgetsChild.jsm b/browser/actors/UAWidgetsChild.jsm new file mode 100644 index 000000000000..f70e44433713 --- /dev/null +++ b/browser/actors/UAWidgetsChild.jsm @@ -0,0 +1,87 @@ +/* vim: set ts=2 sw=2 sts=2 et tw=80: */ +/* 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/. */ +"use strict"; + +var EXPORTED_SYMBOLS = ["UAWidgetsChild"]; + +ChromeUtils.import("resource://gre/modules/ActorChild.jsm"); +ChromeUtils.import("resource://gre/modules/Services.jsm"); + +class UAWidgetsChild extends ActorChild { + constructor(mm) { + super(mm); + + this.widgets = new WeakMap(); + } + + handleEvent(aEvent) { + switch (aEvent.type) { + case "UAWidgetBindToTree": + case "UAWidgetAttributeChanged": + this.setupOrNotifyWidget(aEvent.target); + break; + case "UAWidgetUnbindFromTree": + this.teardownWidget(aEvent.target); + break; + } + } + + setupOrNotifyWidget(aElement) { + let widget = this.widgets.get(aElement); + if (!widget) { + this.setupWidget(aElement); + return; + } + if (typeof widget.wrappedJSObject.onattributechange == "function") { + widget.wrappedJSObject.onattributechange(); + } + } + + setupWidget(aElement) { + let uri; + let widgetName; + switch (aElement.localName) { + case "video": + case "audio": + uri = "chrome://global/content/elements/videocontrols.js"; + widgetName = "VideoControlsPageWidget"; + break; + case "input": + // TODO (datetimebox) + break; + case "applet": + case "embed": + case "object": + // TODO (pluginProblems) + break; + } + + if (!uri || !widgetName) { + return; + } + + let shadowRoot = aElement.openOrClosedShadowRoot; + let sandbox = aElement.nodePrincipal.isSystemPrincipal ? + Object.create(null) : Cu.getUAWidgetScope(aElement.nodePrincipal); + + 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.wrappedJSObject.destructor == "function") { + widget.wrappedJSObject.destructor(); + } + this.widgets.delete(aElement); + } +} diff --git a/browser/actors/moz.build b/browser/actors/moz.build index aad29fc11c5f..289cd21702ad 100644 --- a/browser/actors/moz.build +++ b/browser/actors/moz.build @@ -37,6 +37,7 @@ FINAL_TARGET_FILES.actors += [ 'PageMetadataChild.jsm', 'PageStyleChild.jsm', 'PluginChild.jsm', + 'UAWidgetsChild.jsm', 'URIFixupChild.jsm', 'WebRTCChild.jsm', ] diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index 1c87273c9e13..0959c8625a33 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -230,6 +230,18 @@ let ACTORS = { }, }, + UAWidgets: { + child: { + module: "resource:///actors/UAWidgetsChild.jsm", + group: "browsers", + events: { + "UAWidgetBindToTree": {}, + "UAWidgetAttributeChanged": {}, + "UAWidgetUnbindFromTree": {} + }, + } + }, + UITour: { child: { module: "resource:///modules/UITourChild.jsm", diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h index b3a6e957108a..a5be31af9a28 100644 --- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -163,6 +163,8 @@ public: !BasePrincipal::Cast(aDocumentPrincipal)->AddonPolicy()); } + uint32_t GetOriginNoSuffixHash() const { return mOriginNoSuffix->hash(); } + protected: virtual ~BasePrincipal(); diff --git a/js/xpconnect/idl/xpccomponents.idl b/js/xpconnect/idl/xpccomponents.idl index 6c6663762a86..66642e547845 100644 --- a/js/xpconnect/idl/xpccomponents.idl +++ b/js/xpconnect/idl/xpccomponents.idl @@ -19,6 +19,7 @@ interface nsIJSCID; interface nsIJSIID; interface nsIPrincipal; interface nsIStackFrame; +webidl Element; /** * interface of Components.interfacesByID @@ -175,6 +176,13 @@ interface nsIXPCComponents_Utils : nsISupports [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 nsIPrincipal principal); + /* * getSandboxMetadata is designed to be called from JavaScript only. * diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 6c524316a0bb..40fb9bfb0d91 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -2176,6 +2176,21 @@ nsXPCComponents_Utils::EvalInSandbox(const nsAString& source, return xpc::EvalInSandbox(cx, sandbox, source, filename, lineNo, retval); } +NS_IMETHODIMP +nsXPCComponents_Utils::GetUAWidgetScope(nsIPrincipal* principal, + JSContext* cx, + MutableHandleValue rval) +{ + rval.set(UndefinedValue()); + + JSObject* scope = XPCJSRuntime::Get()->GetUAWidgetScope(cx, principal); + 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) diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 8fe40cecbfb1..ffab06250969 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -898,6 +898,7 @@ XPCJSRuntime::WeakPointerZonesCallback(JSContext* cx, void* data) XPCJSRuntime* self = static_cast(data); self->mWrappedJSMap->UpdateWeakPointersAfterGC(); + self->mUAWidgetScopeMap.sweep(); XPCWrappedNativeScope::UpdateWeakPointersInAllScopesAfterGC(); } @@ -2827,6 +2828,7 @@ XPCJSRuntime::XPCJSRuntime(JSContext* aCx) mWrappedJSRoots(nullptr), mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()) { + MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.init()); MOZ_COUNT_CTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime); } @@ -3139,6 +3141,43 @@ XPCJSRuntime::RemoveGCCallback(xpcGCCallback cb) } } +JSObject* +XPCJSRuntime::GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal) +{ + MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(principal), + "Running UA Widget in chrome"); + + RefPtr key = BasePrincipal::Cast(principal); + if (Principal2JSObjectMap::Ptr p = mUAWidgetScopeMap.lookup(key)) { + return p->value(); + } + + SandboxOptions options; + options.sandboxName.AssignLiteral("UA Widget Scope"); + options.wantXrays = false; + options.wantComponents = false; + + // Use an ExpandedPrincipal to create asymmetric security. + MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal)); + nsTArray> principalAsArray(1); + principalAsArray.AppendElement(principal); + RefPtr ep = + ExpandedPrincipal::Create(principalAsArray, + principal->OriginAttributesRef()); + + // Create the sandbox. + RootedValue v(cx); + nsresult rv = CreateSandboxObject(cx, &v, + static_cast(ep), + options); + NS_ENSURE_SUCCESS(rv, nullptr); + JSObject* scope = &v.toObject(); + + MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.putNew(key, scope)); + + return scope; +} + void XPCJSRuntime::InitSingletonScopes() { diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index 76a6883151a0..52ba6e6bb3a3 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -92,6 +92,8 @@ #include #include "xpcpublic.h" +#include "js/HashTable.h" +#include "js/GCHashTable.h" #include "js/TracingAPI.h" #include "js/WeakMapPtr.h" #include "PLDHashTable.h" @@ -572,6 +574,8 @@ public: void AddGCCallback(xpcGCCallback cb); void RemoveGCCallback(xpcGCCallback cb); + JSObject* GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal); + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); JSObject* UnprivilegedJunkScope() { return mUnprivilegedJunkScope; } @@ -595,11 +599,35 @@ private: jsid mStrIDs[XPCJSContext::IDX_TOTAL_COUNT]; JS::Value mStrJSVals[XPCJSContext::IDX_TOTAL_COUNT]; + struct Hasher { + typedef RefPtr 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* /* unused */, JS::Heap* value) { + return JS::GCPolicy>::needsSweep(value); + } + }; + + typedef JS::GCHashMap, + JS::Heap, + Hasher, + js::SystemAllocPolicy, + SweepPolicy> Principal2JSObjectMap; + JSObject2WrappedJSMap* mWrappedJSMap; IID2WrappedJSClassMap* mWrappedJSClassMap; IID2NativeInterfaceMap* mIID2NativeInterfaceMap; ClassInfo2NativeSetMap* mClassInfo2NativeSetMap; NativeSetMap* mNativeSetMap; + Principal2JSObjectMap mUAWidgetScopeMap; XPCWrappedNativeProtoMap* mDyingWrappedNativeProtoMap; bool mGCIsRunning; nsTArray mNativesToReleaseArray;