Bug 1431255 - Part III, Create per-origin sandboxes from XPCJSRuntime and load UA widgets scripts r=bholley,jaws,sfink

This patch creates the basic structure on how the widget scripts can be loaded
and be pointed to the Shadow Root, from the UAWidgetsChild.jsm.

The UAWidgetsClass 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

--HG--
extra : rebase_source : a62b0a22195f09cdb508df72c954e20d18c7bf68
This commit is contained in:
Timothy Guan-tin Chien 2018-06-27 11:34:07 -07:00
parent 8cc930296b
commit bfd7aeb85d
8 changed files with 192 additions and 0 deletions

View File

@ -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);
}
}

View File

@ -37,6 +37,7 @@ FINAL_TARGET_FILES.actors += [
'PageMetadataChild.jsm',
'PageStyleChild.jsm',
'PluginChild.jsm',
'UAWidgetsChild.jsm',
'URIFixupChild.jsm',
'WebRTCChild.jsm',
]

View File

@ -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",

View File

@ -163,6 +163,8 @@ public:
!BasePrincipal::Cast(aDocumentPrincipal)->AddonPolicy());
}
uint32_t GetOriginNoSuffixHash() const { return mOriginNoSuffix->hash(); }
protected:
virtual ~BasePrincipal();

View File

@ -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.
*

View File

@ -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)

View File

@ -898,6 +898,7 @@ XPCJSRuntime::WeakPointerZonesCallback(JSContext* cx, void* data)
XPCJSRuntime* self = static_cast<XPCJSRuntime*>(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<BasePrincipal> 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<nsCOMPtr<nsIPrincipal>> principalAsArray(1);
principalAsArray.AppendElement(principal);
RefPtr<ExpandedPrincipal> ep =
ExpandedPrincipal::Create(principalAsArray,
principal->OriginAttributesRef());
// Create the sandbox.
RootedValue v(cx);
nsresult rv = CreateSandboxObject(cx, &v,
static_cast<nsIExpandedPrincipal*>(ep),
options);
NS_ENSURE_SUCCESS(rv, nullptr);
JSObject* scope = &v.toObject();
MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.putNew(key, scope));
return scope;
}
void
XPCJSRuntime::InitSingletonScopes()
{

View File

@ -92,6 +92,8 @@
#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"
@ -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<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;