Bug 1130028 - Custom elements, set registered prototype in compartment of caller of registerElement. r=mrbkap

This commit is contained in:
William Chen 2015-03-02 09:48:30 -08:00
parent 71814e4b31
commit 0e7b8a00b1
8 changed files with 194 additions and 47 deletions

View File

@ -417,12 +417,16 @@ Element::WrapObject(JSContext *aCx)
CustomElementData* data = GetCustomElementData();
if (obj && data) {
// If this is a registered custom element then fix the prototype.
JSAutoCompartment ac(aCx, obj);
nsDocument* document = static_cast<nsDocument*>(OwnerDoc());
JS::Rooted<JSObject*> prototype(aCx);
document->GetCustomPrototype(NodeInfo()->NamespaceID(), data->mType, &prototype);
if (prototype) {
if (!JS_WrapObject(aCx, &prototype) || !JS_SetPrototype(aCx, obj, prototype)) {
// We want to set the custom prototype in the compartment where it was
// registered. In the case that |obj| and |prototype| are in different
// compartments, this will set the prototype on the |obj|'s wrapper and
// thus only visible in the wrapper's compartment.
JSAutoCompartment ac(aCx, prototype);
if (!JS_WrapObject(aCx, &obj) || !JS_SetPrototype(aCx, obj, prototype)) {
dom::Throw(aCx, NS_ERROR_FAILURE);
return nullptr;
}

View File

@ -6192,16 +6192,30 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
int32_t namespaceID = kNameSpaceID_XHTML;
JS::Rooted<JSObject*> protoObject(aCx);
{
JSAutoCompartment ac(aCx, global);
JS::Rooted<JSObject*> htmlProto(aCx);
JS::Rooted<JSObject*> svgProto(aCx);
{
JSAutoCompartment ac(aCx, global);
JS::Handle<JSObject*> htmlProto(
HTMLElementBinding::GetProtoObjectHandle(aCx, global));
if (!htmlProto) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
htmlProto = HTMLElementBinding::GetProtoObjectHandle(aCx, global);
if (!htmlProto) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
svgProto = SVGElementBinding::GetProtoObjectHandle(aCx, global);
if (!svgProto) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
}
if (!aOptions.mPrototype) {
if (!JS_WrapObject(aCx, &htmlProto)) {
rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
}
protoObject = JS_NewObjectWithGivenProto(aCx, nullptr, htmlProto);
if (!protoObject) {
rv.Throw(NS_ERROR_UNEXPECTED);
@ -6210,19 +6224,11 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
} else {
protoObject = aOptions.mPrototype;
// We are already operating on the document's (/global's) compartment. Let's
// get a view of the passed in proto from this compartment.
if (!JS_WrapObject(aCx, &protoObject)) {
rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
}
// We also need an unwrapped version of it for various checks.
JS::Rooted<JSObject*> protoObjectUnwrapped(aCx,
js::CheckedUnwrap(protoObject));
// Get the unwrapped prototype to do some checks.
JS::Rooted<JSObject*> protoObjectUnwrapped(aCx, js::CheckedUnwrap(protoObject));
if (!protoObjectUnwrapped) {
// If the documents compartment does not have same origin access
// to the compartment of the proto we should just throw.
// If the caller's compartment does not have permission to access the
// unwrapped prototype then throw.
rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
@ -6238,7 +6244,7 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
JS::Rooted<JSPropertyDescriptor> descRoot(aCx);
JS::MutableHandle<JSPropertyDescriptor> desc(&descRoot);
// This check will go through a wrapper, but as we checked above
// This check may go through a wrapper, but as we checked above
// it should be transparent or an xray. This should be fine for now,
// until the spec is sorted out.
if (!JS_GetPropertyDescriptor(aCx, protoObject, "constructor", desc)) {
@ -6251,15 +6257,13 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
return;
}
JS::Handle<JSObject*> svgProto(
SVGElementBinding::GetProtoObjectHandle(aCx, global));
if (!svgProto) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
JS::Rooted<JSObject*> protoProto(aCx, protoObject);
if (!JS_WrapObject(aCx, &htmlProto) || !JS_WrapObject(aCx, &svgProto)) {
rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
}
JS::Rooted<JSObject*> protoProto(aCx, protoObject);
// If PROTOTYPE's interface inherits from SVGElement, set NAMESPACE to SVG
// Namespace.
while (protoProto) {
@ -6277,7 +6281,7 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
return;
}
}
}
} // Done with the checks, leave prototype's compartment.
// If name was provided and not null...
if (!lcName.IsEmpty()) {
@ -6315,22 +6319,25 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
}
} // Leaving the document's compartment for the LifecycleCallbacks init
JS::Rooted<JSObject*> wrappedProto(aCx, protoObject);
if (!JS_WrapObject(aCx, &wrappedProto)) {
rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
}
// Note: We call the init from the caller compartment here
nsAutoPtr<LifecycleCallbacks> callbacksHolder(new LifecycleCallbacks());
JS::RootedValue rootedv(aCx, JS::ObjectValue(*protoObject));
JS::RootedValue rootedv(aCx, JS::ObjectValue(*wrappedProto));
if (!JS_WrapValue(aCx, &rootedv) || !callbacksHolder->Init(aCx, rootedv)) {
rv.Throw(NS_ERROR_FAILURE);
return;
}
// Entering the global's compartment again
JSAutoCompartment ac(aCx, global);
// Associate the definition with the custom element.
CustomElementHashKey key(namespaceID, typeAtom);
LifecycleCallbacks* callbacks = callbacksHolder.forget();
CustomElementDefinition* definition =
new CustomElementDefinition(protoObject,
new CustomElementDefinition(wrappedProto,
typeAtom,
nameAtom,
callbacks,
@ -6360,9 +6367,13 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
CallQueryInterface(elem, &cache);
MOZ_ASSERT(cache, "Element doesn't support wrapper cache?");
// We want to set the custom prototype in the caller's comparment.
// In the case that element is in a different compartment,
// this will set the prototype on the element's wrapper and
// thus only visible in the wrapper's compartment.
JS::RootedObject wrapper(aCx);
if ((wrapper = cache->GetWrapper())) {
if (!JS_SetPrototype(aCx, wrapper, protoObject)) {
if ((wrapper = cache->GetWrapper()) && JS_WrapObject(aCx, &wrapper)) {
if (!JS_SetPrototype(aCx, wrapper, wrappedProto)) {
continue;
}
}
@ -6371,23 +6382,38 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
}
}
// Create constructor to return. Store the name of the custom element as the
// name of the function.
JSFunction* constructor = JS_NewFunction(aCx, nsDocument::CustomElementConstructor, 0,
JSFUN_CONSTRUCTOR,
NS_ConvertUTF16toUTF8(lcType).get());
if (!constructor) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
JS::Rooted<JSFunction*> constructor(aCx);
{
// Go into the document's global compartment when creating the constructor
// function because we want to get the correct document (where the
// definition is registered) when it is called.
JSAutoCompartment ac(aCx, global);
// Create constructor to return. Store the name of the custom element as the
// name of the function.
constructor = JS_NewFunction(aCx, nsDocument::CustomElementConstructor, 0,
JSFUN_CONSTRUCTOR,
NS_ConvertUTF16toUTF8(lcType).get());
if (!constructor) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
}
JS::Rooted<JSObject*> constructorObj(aCx, JS_GetFunctionObject(constructor));
if (!JS_LinkConstructorAndPrototype(aCx, constructorObj, protoObject)) {
JS::Rooted<JSObject*> wrappedConstructor(aCx);
wrappedConstructor = JS_GetFunctionObject(constructor);
if (!JS_WrapObject(aCx, &wrappedConstructor)) {
rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
}
aRetval.set(constructorObj);
if (!JS_LinkConstructorAndPrototype(aCx, wrappedConstructor, protoObject)) {
rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
}
aRetval.set(wrappedConstructor);
}
void

View File

@ -409,7 +409,8 @@ protected:
CandidateMap;
// Hashtable for custom element definitions in web components.
// Custom prototypes are in the document's compartment.
// Custom prototypes are stored in the compartment where
// registerElement was called.
DefinitionMap mCustomDefinitions;
// The "upgrade candidates map" from the web components spec. Maps from a

View File

@ -18,6 +18,8 @@ support-files =
file_bug1139964.xul
fileconstructor_file.png
frame_bug814638.xul
frame_registerElement_content.html
registerElement_ep.js
host_bug814638.xul
window_nsITextInputProcessor.xul
title_window.xul
@ -61,6 +63,8 @@ skip-if = buildapp == 'mulet'
[test_cpows.xul]
skip-if = buildapp == 'mulet'
[test_document_register.xul]
[test_registerElement_content.xul]
[test_registerElement_ep.xul]
[test_domparsing.xul]
[test_fileconstructor.xul]
[test_fileconstructor_tempfile.xul]

View File

@ -0,0 +1,5 @@
<html>
<body>
<x-bar></x-bar>
</body>
</html>

View File

@ -0,0 +1,8 @@
var proto = Object.create(HTMLElement.prototype);
proto.magicNumber = 42;
proto.createdCallback = function() {
finishTest(this.magicNumber === 42);
};
document.registerElement("x-foo", { prototype: proto });
document.createElement("x-foo");

View File

@ -0,0 +1,55 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1130028
-->
<window title="Mozilla Bug 1130028"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1130028"
target="_blank">Mozilla Bug 1130028</a>
<iframe onload="startTests()" id="frame" src="http://example.com/chrome/dom/base/test/chrome/frame_registerElement_content.html"></iframe>
</body>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
/** Test for Bug 1130028 **/
SimpleTest.waitForExplicitFinish();
var createdCallbackCount = 0;
// Callback should be called once by element created in chrome,
// and once by element created in content.
function createdCallbackCalled() {
createdCallbackCount++;
ok(true, "Created callback called, should be called twice in test.");
is(this.magicNumber, 42, "Callback should be able to see the custom prototype.");
if (createdCallbackCount == 2) {
SimpleTest.finish();
}
}
function startTests() {
var frame = $("frame");
var c = frame.contentDocument.registerElement("x-foo");
var elem = new c();
is(elem.tagName, "X-FOO", "Constructor should create an x-foo element.");
var proto = Object.create(frame.contentWindow.HTMLElement.prototype);
proto.magicNumber = 42;
proto.createdCallback = createdCallbackCalled;
frame.contentDocument.registerElement("x-bar", { prototype: proto });
frame.contentDocument.createElement("x-bar");
}
]]></script>
</window>

View File

@ -0,0 +1,44 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1130028
-->
<window title="Mozilla Bug 1130028"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1130028"
target="_blank">Mozilla Bug 1130028</a>
<iframe onload="startTests()" id="frame" src="http://example.com/chrome/dom/base/test/chrome/frame_registerElement_content.html"></iframe>
</body>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
Components.utils.import("resource://gre/modules/Services.jsm");
/** Test for Bug 1130028 **/
SimpleTest.waitForExplicitFinish();
function finishTest(canSeePrototype) {
ok(true, "createdCallback called when reigsterElement was called with an extended principal.");
ok(canSeePrototype, "createdCallback should be able to see custom prototype.");
SimpleTest.finish();
}
function startTests() {
var frame = $("frame");
// Create a sandbox with an extended principal then run a script that registers a custom element in the sandbox.
var sandbox = Components.utils.Sandbox([frame.contentWindow], { sandboxPrototype: frame.contentWindow });
sandbox.finishTest = finishTest;
Services.scriptloader.loadSubScript("chrome://mochitests/content/chrome/dom/base/test/chrome/registerElement_ep.js", sandbox);
}
]]></script>
</window>