From 0e7b8a00b1f58f4d4edc9e0d6bab8b3d3ff71b8b Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 2 Mar 2015 09:48:30 -0800 Subject: [PATCH] Bug 1130028 - Custom elements, set registered prototype in compartment of caller of registerElement. r=mrbkap --- dom/base/Element.cpp | 8 +- dom/base/nsDocument.cpp | 114 +++++++++++------- dom/base/nsDocument.h | 3 +- dom/base/test/chrome/chrome.ini | 4 + .../chrome/frame_registerElement_content.html | 5 + dom/base/test/chrome/registerElement_ep.js | 8 ++ .../chrome/test_registerElement_content.xul | 55 +++++++++ .../test/chrome/test_registerElement_ep.xul | 44 +++++++ 8 files changed, 194 insertions(+), 47 deletions(-) create mode 100644 dom/base/test/chrome/frame_registerElement_content.html create mode 100644 dom/base/test/chrome/registerElement_ep.js create mode 100644 dom/base/test/chrome/test_registerElement_content.xul create mode 100644 dom/base/test/chrome/test_registerElement_ep.xul diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 426e9baa591e..abb740915451 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -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(OwnerDoc()); JS::Rooted 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; } diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index debe8b3aa8ef..026e4c0b3ea1 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -6192,16 +6192,30 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType, int32_t namespaceID = kNameSpaceID_XHTML; JS::Rooted protoObject(aCx); { - JSAutoCompartment ac(aCx, global); + JS::Rooted htmlProto(aCx); + JS::Rooted svgProto(aCx); + { + JSAutoCompartment ac(aCx, global); - JS::Handle 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 protoObjectUnwrapped(aCx, - js::CheckedUnwrap(protoObject)); + // Get the unwrapped prototype to do some checks. + JS::Rooted 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 descRoot(aCx); JS::MutableHandle 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 svgProto( - SVGElementBinding::GetProtoObjectHandle(aCx, global)); - if (!svgProto) { - rv.Throw(NS_ERROR_OUT_OF_MEMORY); + JS::Rooted protoProto(aCx, protoObject); + + if (!JS_WrapObject(aCx, &htmlProto) || !JS_WrapObject(aCx, &svgProto)) { + rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } - JS::Rooted 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 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 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 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 constructorObj(aCx, JS_GetFunctionObject(constructor)); - if (!JS_LinkConstructorAndPrototype(aCx, constructorObj, protoObject)) { + JS::Rooted 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 diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 3cbd872af6be..f662db15fa91 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -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 diff --git a/dom/base/test/chrome/chrome.ini b/dom/base/test/chrome/chrome.ini index d43b16b353e1..ddc219442020 100644 --- a/dom/base/test/chrome/chrome.ini +++ b/dom/base/test/chrome/chrome.ini @@ -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] diff --git a/dom/base/test/chrome/frame_registerElement_content.html b/dom/base/test/chrome/frame_registerElement_content.html new file mode 100644 index 000000000000..aa1d75863daa --- /dev/null +++ b/dom/base/test/chrome/frame_registerElement_content.html @@ -0,0 +1,5 @@ + + + + + diff --git a/dom/base/test/chrome/registerElement_ep.js b/dom/base/test/chrome/registerElement_ep.js new file mode 100644 index 000000000000..de32ba51c699 --- /dev/null +++ b/dom/base/test/chrome/registerElement_ep.js @@ -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"); diff --git a/dom/base/test/chrome/test_registerElement_content.xul b/dom/base/test/chrome/test_registerElement_content.xul new file mode 100644 index 000000000000..9a918f2d7793 --- /dev/null +++ b/dom/base/test/chrome/test_registerElement_content.xul @@ -0,0 +1,55 @@ + + + + + + + + + + Mozilla Bug 1130028 + + + + + + diff --git a/dom/base/test/chrome/test_registerElement_ep.xul b/dom/base/test/chrome/test_registerElement_ep.xul new file mode 100644 index 000000000000..6f17452685e6 --- /dev/null +++ b/dom/base/test/chrome/test_registerElement_ep.xul @@ -0,0 +1,44 @@ + + + + + + + + + + Mozilla Bug 1130028 + + + + + +