mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 05:45:37 +00:00
Bug 1130028 - Custom elements, set registered prototype in compartment of caller of registerElement. r=mrbkap
This commit is contained in:
parent
71814e4b31
commit
0e7b8a00b1
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
5
dom/base/test/chrome/frame_registerElement_content.html
Normal file
5
dom/base/test/chrome/frame_registerElement_content.html
Normal file
@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
<x-bar></x-bar>
|
||||
</body>
|
||||
</html>
|
8
dom/base/test/chrome/registerElement_ep.js
Normal file
8
dom/base/test/chrome/registerElement_ep.js
Normal 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");
|
55
dom/base/test/chrome/test_registerElement_content.xul
Normal file
55
dom/base/test/chrome/test_registerElement_content.xul
Normal 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>
|
44
dom/base/test/chrome/test_registerElement_ep.xul
Normal file
44
dom/base/test/chrome/test_registerElement_ep.xul
Normal 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>
|
Loading…
Reference in New Issue
Block a user