Bug 1480465: Infer the namespace for custom elements at definition time by following the class hierarchy. r=smaug

When a custom element is defined we can check whether its class is an instance
of XULElement or HTMLElement and tag the defintion with a namespace accordingly.
This allows us to know the correct namespace for the custom element when
created.

Differential Revision: https://phabricator.services.mozilla.com/D2680

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Dave Townsend 2018-08-15 10:31:16 +00:00
parent 2022970c76
commit fd8d6b1590
9 changed files with 359 additions and 9 deletions

View File

@ -10,6 +10,7 @@
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/dom/CustomElementRegistryBinding.h"
#include "mozilla/dom/HTMLElementBinding.h"
#include "mozilla/dom/XULElementBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/WebComponentsBinding.h"
#include "mozilla/dom/DocGroup.h"
@ -356,6 +357,7 @@ CustomElementRegistry::RunCustomElementCreationCallback::Run()
CustomElementDefinition*
CustomElementRegistry::LookupCustomElementDefinition(nsAtom* aNameAtom,
int32_t aNameSpaceID,
nsAtom* aTypeAtom)
{
CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom);
@ -373,7 +375,7 @@ CustomElementRegistry::LookupCustomElementDefinition(nsAtom* aNameAtom,
}
}
if (data && data->mLocalName == aNameAtom) {
if (data && data->mLocalName == aNameAtom && data->mNamespaceID == aNameSpaceID) {
return data;
}
@ -688,6 +690,24 @@ CustomElementRegistry::GetDocGroup() const
return mWindow ? mWindow->GetDocGroup() : nullptr;
}
int32_t
CustomElementRegistry::InferNamespace(JSContext* aCx,
JS::Handle<JSObject*> constructor)
{
JSObject* XULConstructor = XULElement_Binding::GetConstructorObject(aCx);
JS::Rooted<JSObject*> proto(aCx, constructor);
while (proto) {
if (proto == XULConstructor) {
return kNameSpaceID_XUL;
}
JS_GetPrototype(aCx, proto, &proto);
}
return kNameSpaceID_XHTML;
}
// https://html.spec.whatwg.org/multipage/scripting.html#element-definition
void
CustomElementRegistry::Define(JSContext* aCx,
@ -720,12 +740,13 @@ CustomElementRegistry::Define(JSContext* aCx,
return;
}
int32_t nameSpaceID = InferNamespace(aCx, constructor);
/**
* 2. If name is not a valid custom element name, then throw a "SyntaxError"
* DOMException and abort these steps.
*/
nsIDocument* doc = mWindow->GetExtantDoc();
uint32_t nameSpaceID = doc ? doc->GetDefaultNamespaceID() : kNameSpaceID_XHTML;
RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
@ -945,6 +966,7 @@ CustomElementRegistry::Define(JSContext* aCx,
RefPtr<CustomElementDefinition> definition =
new CustomElementDefinition(nameAtom,
localNameAtom,
nameSpaceID,
&aFunctionConstructor,
std::move(observedAttributes),
std::move(callbacksHolder));
@ -1471,11 +1493,13 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CustomElementDefinition, Release)
CustomElementDefinition::CustomElementDefinition(nsAtom* aType,
nsAtom* aLocalName,
int32_t aNamespaceID,
Function* aConstructor,
nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
UniquePtr<LifecycleCallbacks>&& aCallbacks)
: mType(aType),
mLocalName(aLocalName),
mNamespaceID(aNamespaceID),
mConstructor(new CustomElementConstructor(aConstructor)),
mObservedAttributes(std::move(aObservedAttributes)),
mCallbacks(std::move(aCallbacks))

View File

@ -157,6 +157,7 @@ struct CustomElementDefinition
CustomElementDefinition(nsAtom* aType,
nsAtom* aLocalName,
int32_t aNamespaceID,
Function* aConstructor,
nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
UniquePtr<LifecycleCallbacks>&& aCallbacks);
@ -168,6 +169,9 @@ struct CustomElementDefinition
// The localname to (e.g. <button is=type> -- this would be button).
RefPtr<nsAtom> mLocalName;
// The namespace for this custom element
int32_t mNamespaceID;
// The custom element constructor.
RefPtr<CustomElementConstructor> mConstructor;
@ -399,7 +403,7 @@ public:
* https://html.spec.whatwg.org/#look-up-a-custom-element-definition
*/
CustomElementDefinition* LookupCustomElementDefinition(
nsAtom* aNameAtom, nsAtom* aTypeAtom);
nsAtom* aNameAtom, int32_t aNameSpaceID, nsAtom* aTypeAtom);
CustomElementDefinition* LookupCustomElementDefinition(
JSContext* aCx, JSObject *aConstructor) const;
@ -555,6 +559,8 @@ private:
CustomElementRegistry* mRegistry;
};
int32_t InferNamespace(JSContext* aCx, JS::Handle<JSObject*> constructor);
public:
nsISupports* GetParentObject() const;

View File

@ -10002,6 +10002,15 @@ nsContentUtils::NewXULOrHTMLElement(Element** aResult, mozilla::dom::NodeInfo* a
nsIGlobalObject* global;
if (aFromParser == dom::NOT_FROM_PARSER) {
global = GetEntryGlobal();
// XUL documents always use NOT_FROM_PARSER for non-XUL elements. We can
// get the global from the document in that case.
if (!global) {
nsIDocument* doc = nodeInfo->GetDocument();
if (doc && doc->IsXULDocument()) {
global = doc->GetScopeObject();
}
}
} else {
global = nodeInfo->GetDocument()->GetScopeObject();
}
@ -10122,7 +10131,7 @@ nsContentUtils::LookupCustomElementDefinition(nsIDocument* aDoc,
return nullptr;
}
return registry->LookupCustomElementDefinition(aNameAtom, aTypeAtom);
return registry->LookupCustomElementDefinition(aNameAtom, aNameSpaceID, aTypeAtom);
}
/* static */ void

View File

@ -3811,10 +3811,7 @@ HTMLConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
// determination of what sort of element we're planning to construct.
// Technically, this should happen (implicitly) in step 8, but this
// determination is side-effect-free, so it's OK.
int32_t ns = doc->GetDefaultNamespaceID();
if (ns != kNameSpaceID_XUL) {
ns = kNameSpaceID_XHTML;
}
int32_t ns = definition->mNamespaceID;
constructorGetterCallback cb = nullptr;
if (ns == kNameSpaceID_XUL) {

View File

@ -3,3 +3,6 @@ prefs =
dom.webcomponents.customelements.enabled=false
[test_xul_custom_element.xul]
[test_custom_element_namespace.html]
[test_custom_element_namespace.xhtml]
[test_custom_element_namespace.xul]

View File

@ -0,0 +1,95 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Custom Elements in an HTML document</title>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script>
SimpleTest.waitForExplicitFinish();
const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
class TestXULCustomElement extends XULElement {
constructor() {
super();
}
get connected() {
return true;
}
}
customElements.define("test-xul-element", TestXULCustomElement);
class TestHTMLCustomElement extends HTMLElement {
constructor() {
super();
}
get connected() {
return true;
}
}
customElements.define("test-html-element", TestHTMLCustomElement);
function checkElement(element, ns, connected, type) {
is(element.namespaceURI, ns, `${type} should have the correct namespace`);
if (connected) {
ok(element.connected, `${type} should have applied the class`);
} else {
is(element.connected, undefined, `${type} should not have applied the class`);
}
}
function runTest() {
let element = new TestXULCustomElement();
checkElement(element, XUL_NS, true, "instantiated XUL");
element = document.getElementById("xul2");
checkElement(element, HTML_NS, false, "parsed XUL as HTML");
element = document.createElement("test-xul-element");
checkElement(element, HTML_NS, false, "document.createElement(XUL)");
element = document.createXULElement("test-xul-element");
checkElement(element, XUL_NS, true, "document.createXULElement(XUL)");
element = document.createElementNS(XUL_NS, "test-xul-element");
checkElement(element, XUL_NS, true, "document.createElementNS(XUL, XUL)");
element = document.createElementNS(HTML_NS, "test-xul-element");
checkElement(element, HTML_NS, false, "document.createElementNS(HTML, XUL)");
element = new TestHTMLCustomElement();
checkElement(element, HTML_NS, true, "instantiated HTML");
element = document.getElementById("html2");
checkElement(element, HTML_NS, true, "parsed HTML as HTML");
element = document.createElement("test-html-element");
checkElement(element, HTML_NS, true, "document.createElement(HTML)");
element = document.createXULElement("test-html-element");
checkElement(element, XUL_NS, false, "document.createXULElement(HTML)");
element = document.createElementNS(XUL_NS, "test-html-element");
checkElement(element, XUL_NS, false, "document.createElementNS(XUL, HTML)");
element = document.createElementNS(HTML_NS, "test-html-element");
checkElement(element, HTML_NS, true, "document.createElementNS(HTML, HTML)");
SimpleTest.finish();
}
</script>
</head>
<body onload="runTest();">
<p id="display"></p>
<div id="content">
<test-xul-element id="xul2"/>
<test-html-element id="html2"/>
</div>
<pre id="test"></pre>
</body>
</html>

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Custom Elements in an XHTML document</title>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script>
SimpleTest.waitForExplicitFinish();
const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
class TestXULCustomElement extends XULElement {
constructor() {
super();
}
get connected() {
return true;
}
}
customElements.define("test-xul-element", TestXULCustomElement);
class TestHTMLCustomElement extends HTMLElement {
constructor() {
super();
}
get connected() {
return true;
}
}
customElements.define("test-html-element", TestHTMLCustomElement);
function checkElement(element, ns, connected, type) {
is(element.namespaceURI, ns, `${type} should have the correct namespace`);
if (connected) {
ok(element.connected, `${type} should have applied the class`);
} else {
is(element.connected, undefined, `${type} should not have applied the class`);
}
}
function runTest() {
let element = new TestXULCustomElement();
checkElement(element, XUL_NS, true, "instantiated XUL");
element = document.getElementById("xul1");
checkElement(element, XUL_NS, true, "parsed XUL as XUL");
element = document.getElementById("xul2");
checkElement(element, HTML_NS, false, "parsed XUL as HTML");
element = document.createElement("test-xul-element");
checkElement(element, HTML_NS, false, "document.createElement(XUL)");
element = document.createXULElement("test-xul-element");
checkElement(element, XUL_NS, true, "document.createXULElement(XUL)");
element = document.createElementNS(XUL_NS, "test-xul-element");
checkElement(element, XUL_NS, true, "document.createElementNS(XUL, XUL)");
element = document.createElementNS(HTML_NS, "test-xul-element");
checkElement(element, HTML_NS, false, "document.createElementNS(HTML, XUL)");
element = new TestHTMLCustomElement();
checkElement(element, HTML_NS, true, "instantiated HTML");
element = document.getElementById("html1");
checkElement(element, XUL_NS, false, "parsed HTML as XUL");
element = document.getElementById("html2");
checkElement(element, HTML_NS, true, "parsed HTML as HTML");
element = document.createElement("test-html-element");
checkElement(element, HTML_NS, true, "document.createElement(HTML)");
element = document.createXULElement("test-html-element");
checkElement(element, XUL_NS, false, "document.createXULElement(HTML)");
element = document.createElementNS(XUL_NS, "test-html-element");
checkElement(element, XUL_NS, false, "document.createElementNS(XUL, HTML)");
element = document.createElementNS(HTML_NS, "test-html-element");
checkElement(element, HTML_NS, true, "document.createElementNS(HTML, HTML)");
SimpleTest.finish();
}
</script>
</head>
<body onload="runTest();">
<p id="display"></p>
<div id="content">
<test-xul-element xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="xul1"/>
<test-html-element xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="html1"/>
<test-xul-element id="xul2"/>
<test-html-element id="html2"/>
</div>
<pre id="test"></pre>
</body>
</html>

View File

@ -0,0 +1,112 @@
<?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"?>
<window title="XUL Custom Elements"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="runTest();">
<title>Custom Elements in a XUL document</title>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript">
<![CDATA[
SimpleTest.waitForExplicitFinish();
const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
class TestXULCustomElement extends XULElement {
constructor() {
super();
}
get connected() {
return true;
}
}
customElements.define("test-xul-element", TestXULCustomElement);
class TestHTMLCustomElement extends HTMLElement {
constructor() {
super();
}
get connected() {
return true;
}
}
customElements.define("test-html-element", TestHTMLCustomElement);
function checkElement(element, ns, connected, type) {
is(element.namespaceURI, ns, `${type} should have the correct namespace`);
if (connected) {
ok(element.connected, `${type} should have applied the class`);
} else {
is(element.connected, undefined, `${type} should not have applied the class`);
}
}
function runTest() {
let element = new TestXULCustomElement();
checkElement(element, XUL_NS, true, "instantiated XUL");
element = document.getElementById("xul1");
checkElement(element, XUL_NS, true, "parsed XUL as XUL");
element = document.getElementById("xul2");
checkElement(element, HTML_NS, false, "parsed XUL as HTML");
element = document.createElement("test-xul-element");
checkElement(element, XUL_NS, true, "document.createElement(XUL)");
element = document.createXULElement("test-xul-element");
checkElement(element, XUL_NS, true, "document.createXULElement(XUL)");
element = document.createElementNS(XUL_NS, "test-xul-element");
checkElement(element, XUL_NS, true, "document.createElementNS(XUL, XUL)");
element = document.createElementNS(HTML_NS, "test-xul-element");
checkElement(element, HTML_NS, false, "document.createElementNS(HTML, XUL)");
element = new TestHTMLCustomElement();
checkElement(element, HTML_NS, true, "instantiated HTML");
element = document.getElementById("html1");
checkElement(element, XUL_NS, false, "parsed HTML as XUL");
element = document.getElementById("html2");
checkElement(element, HTML_NS, true, "parsed HTML as HTML");
element = document.createElement("test-html-element");
checkElement(element, XUL_NS, false, "document.createElement(HTML)");
element = document.createXULElement("test-html-element");
checkElement(element, XUL_NS, false, "document.createXULElement(HTML)");
element = document.createElementNS(XUL_NS, "test-html-element");
checkElement(element, XUL_NS, false, "document.createElementNS(XUL, HTML)");
element = document.createElementNS(HTML_NS, "test-html-element");
checkElement(element, HTML_NS, true, "document.createElementNS(HTML, HTML)");
SimpleTest.finish();
}
]]>
</script>
<test-xul-element id="xul1"/>
<test-html-element id="html1"/>
<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display"></p>
<div id="content" style="display: none">
<test-xul-element id="xul2"/>
<test-html-element id="html2"/>
</div>
<pre id="test"></pre>
</body>
</window>

View File

@ -382,7 +382,7 @@ nsHtml5TreeOperation::CreateHTMLElement(
dom::CustomElementDefinition* definition = nullptr;
// Avoid overhead by checking if custom elements pref is enabled or not.
if (nsContentUtils::IsCustomElementsEnabled()) {
if (dom::CustomElementRegistry::IsCustomElementEnabled(document)) {
if (aAttributes) {
nsHtml5String is = aAttributes->getValue(nsHtml5AttributeName::ATTR_IS);
if (is) {