Title: [208716] trunk
Revision
208716
Author
[email protected]
Date
2016-11-14 16:27:01 -0800 (Mon, 14 Nov 2016)

Log Message

document.createElementNS doesn't construct a custom element
https://bugs.webkit.org/show_bug.cgi?id=164700

Reviewed by Darin Adler.

Source/WebCore:

Fixed the bug that document.createElementNS doesn't create a custom element or enqueue it to upgrade.

Also made constructCustomElementSynchronously not call the custom element constructors with the element's
local name as the first argument, which was a non-standard behavior added during prototyping.

Test: fast/custom-elements/DOMImplementation-createDocument.html
      fast/custom-elements/document-createElementNS.html

* bindings/js/JSCustomElementInterface.cpp:
(WebCore::JSCustomElementInterface::constructElementWithFallback): Added a variant that takes QualifiedName
instead of AtomicString.
(WebCore::constructCustomElementSynchronously): Don't add the local name as an argument.
* bindings/js/JSCustomElementInterface.h:

* dom/CustomElementRegistry.cpp:
(WebCore::CustomElementRegistry::findInterface): Just find the interface based on the local name after
checking the namespace URI to be that of the XHTML. We need to ignore the prefix for the purpose of looking
up the custom element definition as specified in the latest HTML specification:
https://html.spec.whatwg.org/multipage/scripting.html#look-up-a-custom-element-definition

* dom/DOMImplementation.cpp:
(WebCore::DOMImplementation::createDocument): Added an assertion to make sure we don't invoke scripts while
constructing the document element.

* dom/Document.cpp:
(WebCore::createUpgradeCandidateElement): Made this function create a HTMLUnknownElement instead of returning
nullptr to share more code. Also added a variant which takes QualifiedName.
(WebCore::isValidHTMLElementName): Added; helpers for createHTMLElementWithNameValidation to call isValidName
on Document with the right argument.
(WebCore::createHTMLElementWithNameValidation): Templatized the function to be called with either AtomicString
or QualifiedName for the name.
(WebCore::createFallbackHTMLElement):
(WebCore::Document::createElementNS): Call createHTMLElementWithNameValidation to create a custom element if
possible. This function ends up re-validating the element name before creating a HTMLUnknownElement but that
shouldn't be a common scenario to matter. In fact, createElementNS is a rarely used API.

LayoutTests:

Added W3C style testharness.js tests for createElementNS and DOMImplementation's createDocument.

* fast/custom-elements/DOMImplementation-createDocument-expected.txt: Added.
* fast/custom-elements/DOMImplementation-createDocument.html: Added.
* fast/custom-elements/document-createElementNS-expected.txt: Added.
* fast/custom-elements/document-createElementNS.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (208715 => 208716)


--- trunk/LayoutTests/ChangeLog	2016-11-15 00:19:24 UTC (rev 208715)
+++ trunk/LayoutTests/ChangeLog	2016-11-15 00:27:01 UTC (rev 208716)
@@ -1,3 +1,17 @@
+2016-11-14  Ryosuke Niwa  <[email protected]>
+
+        document.createElementNS doesn't construct a custom element
+        https://bugs.webkit.org/show_bug.cgi?id=164700
+
+        Reviewed by Darin Adler.
+
+        Added W3C style testharness.js tests for createElementNS and DOMImplementation's createDocument.
+
+        * fast/custom-elements/DOMImplementation-createDocument-expected.txt: Added.
+        * fast/custom-elements/DOMImplementation-createDocument.html: Added.
+        * fast/custom-elements/document-createElementNS-expected.txt: Added.
+        * fast/custom-elements/document-createElementNS.html: Added.
+
 2016-11-14  Dean Jackson  <[email protected]>
 
         Handle filter() image type in new CSS Parser

Added: trunk/LayoutTests/fast/custom-elements/DOMImplementation-createDocument-expected.txt (0 => 208716)


--- trunk/LayoutTests/fast/custom-elements/DOMImplementation-createDocument-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/DOMImplementation-createDocument-expected.txt	2016-11-15 00:27:01 UTC (rev 208716)
@@ -0,0 +1,3 @@
+
+PASS document.createElement invoke a custom element constructor with no arguments 
+

Added: trunk/LayoutTests/fast/custom-elements/DOMImplementation-createDocument.html (0 => 208716)


--- trunk/LayoutTests/fast/custom-elements/DOMImplementation-createDocument.html	                        (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/DOMImplementation-createDocument.html	2016-11-15 00:27:01 UTC (rev 208716)
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: document.implementation.createElement must not instantiate a custom element</title>
+<meta name="author" title="Ryosuke Niwa" href=""
+<meta name="assert" content="document.implementation.createElement must not instantiate a custom element">
+<link rel="help" content="https://dom.spec.whatwg.org/#dom-domimplementation-createdocument">
+<link rel="help" content="https://html.spec.whatwg.org/#look-up-a-custom-element-definition">
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+const xhtmlNamespaceURI = 'http://www.w3.org/1999/xhtml';
+
+test_with_window((contentWindow, contentDocument) => {
+    let constructorCount = 0;
+    class MyCustomElement extends contentWindow.HTMLElement {
+        constructor() {
+            super();
+            constructorCount++;
+        }
+    }
+    contentWindow.customElements.define('my-custom-element', MyCustomElement);
+    const instance = contentDocument.createElement('my-custom-element');
+    assert_true(instance instanceof MyCustomElement);
+    assert_equals(constructorCount, 1);
+
+    const newDocument = contentDocument.implementation.createDocument(xhtmlNamespaceURI, 'my-custom-element', null);
+    assert_equals(constructorCount, 1);
+    assert_true(newDocument.documentElement instanceof contentWindow.HTMLElement);
+    assert_equals(newDocument.documentElement.localName, 'my-custom-element');
+    assert_false(newDocument.documentElement instanceof MyCustomElement);
+}, 'document.createElement invoke a custom element constructor with no arguments');
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/fast/custom-elements/document-createElementNS-expected.txt (0 => 208716)


--- trunk/LayoutTests/fast/custom-elements/document-createElementNS-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/document-createElementNS-expected.txt	2016-11-15 00:27:01 UTC (rev 208716)
@@ -0,0 +1,6 @@
+
+PASS document.createElement invoke a custom element constructor with no arguments 
+PASS document.createElementNS must create an instance of custom elements 
+PASS document.createElementNS must set the prefix after constructing a custom element 
+PASS document.createElementNS must use the element returned by the constructor 
+

Added: trunk/LayoutTests/fast/custom-elements/document-createElementNS.html (0 => 208716)


--- trunk/LayoutTests/fast/custom-elements/document-createElementNS.html	                        (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/document-createElementNS.html	2016-11-15 00:27:01 UTC (rev 208716)
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: document.createElementNS must create an element with synchronous custom elements flag set</title>
+<meta name="author" title="Ryosuke Niwa" href=""
+<meta name="assert" content="document.createElementNS must create an element with synchronous custom elements flag set">
+<link rel="help" content="https://dom.spec.whatwg.org/#dom-document-createelementns">
+<link rel="help" content="https://dom.spec.whatwg.org/#concept-create-element">
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+const xhtmlNamespaceURI = 'http://www.w3.org/1999/xhtml';
+
+test_with_window((contentWindow, contentDocument) => {
+    let constructorArguments;
+    class MyCustomElement extends contentWindow.HTMLElement {
+        constructor() {
+            constructorArguments = Array.from(arguments);
+            super();
+        }
+    }
+    contentWindow.customElements.define('my-custom-element', MyCustomElement);
+    const instance = contentDocument.createElement('my-custom-element');
+    assert_true(instance instanceof MyCustomElement);
+    assert_array_equals(constructorArguments, []);
+}, 'document.createElement invoke a custom element constructor with no arguments');
+
+test_with_window((contentWindow, contentDocument) => {
+    class MyCustomElement extends contentWindow.HTMLElement {};
+
+    const upgradeCandidate = contentDocument.createElementNS(xhtmlNamespaceURI, 'my-custom-element');
+    assert_true(upgradeCandidate instanceof contentWindow.HTMLElement);
+    assert_false(upgradeCandidate instanceof MyCustomElement);
+
+    contentWindow.customElements.define('my-custom-element', MyCustomElement);
+    const instance = contentDocument.createElementNS(xhtmlNamespaceURI, 'my-custom-element');
+    assert_true(instance instanceof MyCustomElement);
+    assert_equals(instance.localName, 'my-custom-element');
+    assert_equals(instance.namespaceURI, xhtmlNamespaceURI);
+}, 'document.createElementNS must create an instance of custom elements');
+
+test_with_window((contentWindow, contentDocument) => {
+    class MyCustomElement extends contentWindow.HTMLElement {};
+    contentWindow.customElements.define('element-with-prefix', MyCustomElement);
+
+    const instance = contentDocument.createElementNS(xhtmlNamespaceURI, 'foo:element-with-prefix');
+    assert_true(instance instanceof MyCustomElement);
+    assert_equals(instance.localName, 'element-with-prefix');
+    assert_equals(instance.namespaceURI, xhtmlNamespaceURI);
+    assert_equals(instance.prefix, 'foo');
+}, 'document.createElementNS must set the prefix after constructing a custom element');
+
+test_with_window((contentWindow, contentDocument) => {
+    let innerElement;
+    let innerElementPrefix;
+    let outerElement;
+    let outerElementPrefix;
+    class MyCustomElement extends contentWindow.HTMLElement {
+        constructor(isInnerCall) {
+            if (isInnerCall) {
+                innerElement = super();
+                innerElementPrefix = innerElement.prefix;
+                return innerElement;
+            }
+            outerElement = super();
+            outerElementPrefix = outerElement.prefix;
+            return new MyCustomElement(true);
+        }
+    }
+    contentWindow.customElements.define('custom-element', MyCustomElement);
+
+    const instance = contentDocument.createElementNS(xhtmlNamespaceURI, 'foo:custom-element');
+    assert_true(instance instanceof MyCustomElement);
+    assert_equals(instance.localName, 'custom-element');
+    assert_equals(instance.namespaceURI, xhtmlNamespaceURI);
+    assert_equals(instance.prefix, 'foo');
+    assert_equals(instance, innerElement, 'document.createElementNS must create an element with the synchronous custom elements flag set');
+    assert_equals(innerElementPrefix, null, 'HTML constructor must not set prefix');
+    assert_equals(outerElementPrefix, null, 'HTML constructor must not set prefix');
+}, 'document.createElementNS must use the element returned by the constructor');
+
+</script>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (208715 => 208716)


--- trunk/Source/WebCore/ChangeLog	2016-11-15 00:19:24 UTC (rev 208715)
+++ trunk/Source/WebCore/ChangeLog	2016-11-15 00:27:01 UTC (rev 208716)
@@ -1,3 +1,46 @@
+2016-11-14  Ryosuke Niwa  <[email protected]>
+
+        document.createElementNS doesn't construct a custom element
+        https://bugs.webkit.org/show_bug.cgi?id=164700
+
+        Reviewed by Darin Adler.
+
+        Fixed the bug that document.createElementNS doesn't create a custom element or enqueue it to upgrade.
+
+        Also made constructCustomElementSynchronously not call the custom element constructors with the element's
+        local name as the first argument, which was a non-standard behavior added during prototyping.
+
+        Test: fast/custom-elements/DOMImplementation-createDocument.html
+              fast/custom-elements/document-createElementNS.html
+
+        * bindings/js/JSCustomElementInterface.cpp:
+        (WebCore::JSCustomElementInterface::constructElementWithFallback): Added a variant that takes QualifiedName
+        instead of AtomicString.
+        (WebCore::constructCustomElementSynchronously): Don't add the local name as an argument.
+        * bindings/js/JSCustomElementInterface.h:
+
+        * dom/CustomElementRegistry.cpp:
+        (WebCore::CustomElementRegistry::findInterface): Just find the interface based on the local name after
+        checking the namespace URI to be that of the XHTML. We need to ignore the prefix for the purpose of looking
+        up the custom element definition as specified in the latest HTML specification:
+        https://html.spec.whatwg.org/multipage/scripting.html#look-up-a-custom-element-definition
+
+        * dom/DOMImplementation.cpp:
+        (WebCore::DOMImplementation::createDocument): Added an assertion to make sure we don't invoke scripts while
+        constructing the document element.
+
+        * dom/Document.cpp:
+        (WebCore::createUpgradeCandidateElement): Made this function create a HTMLUnknownElement instead of returning
+        nullptr to share more code. Also added a variant which takes QualifiedName.
+        (WebCore::isValidHTMLElementName): Added; helpers for createHTMLElementWithNameValidation to call isValidName
+        on Document with the right argument.
+        (WebCore::createHTMLElementWithNameValidation): Templatized the function to be called with either AtomicString
+        or QualifiedName for the name.
+        (WebCore::createFallbackHTMLElement):
+        (WebCore::Document::createElementNS): Call createHTMLElementWithNameValidation to create a custom element if
+        possible. This function ends up re-validating the element name before creating a HTMLUnknownElement but that
+        shouldn't be a common scenario to matter. In fact, createElementNS is a rarely used API.
+
 2016-11-14  Chris Dumez  <[email protected]>
 
         Avoid copying attributes vector when constructing a CustomElement in HTMLTreeBuilder

Modified: trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp (208715 => 208716)


--- trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp	2016-11-15 00:19:24 UTC (rev 208715)
+++ trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp	2016-11-15 00:27:01 UTC (rev 208716)
@@ -67,9 +67,24 @@
     element->setIsCustomElementUpgradeCandidate();
     element->setIsFailedCustomElement(*this);
 
-    return element.get();
+    return WTFMove(element);
 }
 
+Ref<Element> JSCustomElementInterface::constructElementWithFallback(Document& document, const QualifiedName& name)
+{
+    if (auto element = tryToConstructCustomElement(document, name.localName())) {
+        if (name.prefix() != nullAtom)
+            element->setPrefix(name.prefix());
+        return element.releaseNonNull();
+    }
+
+    auto element = HTMLUnknownElement::create(name, document);
+    element->setIsCustomElementUpgradeCandidate();
+    element->setIsFailedCustomElement(*this);
+
+    return WTFMove(element);
+}
+
 RefPtr<Element> JSCustomElementInterface::tryToConstructCustomElement(Document& document, const AtomicString& localName)
 {
     if (!canInvokeCallback())
@@ -110,10 +125,8 @@
         return nullptr;
     }
 
+    InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionConstruct(&document, constructType, constructData);
     MarkedArgumentBuffer args;
-    args.append(jsStringWithCache(&state, localName));
-
-    InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionConstruct(&document, constructType, constructData);
     JSValue newElement = construct(&state, constructor, constructType, constructData, args);
     InspectorInstrumentation::didCallFunction(cookie, &document);
     RETURN_IF_EXCEPTION(scope, nullptr);

Modified: trunk/Source/WebCore/bindings/js/JSCustomElementInterface.h (208715 => 208716)


--- trunk/Source/WebCore/bindings/js/JSCustomElementInterface.h	2016-11-15 00:19:24 UTC (rev 208715)
+++ trunk/Source/WebCore/bindings/js/JSCustomElementInterface.h	2016-11-15 00:27:01 UTC (rev 208716)
@@ -59,6 +59,7 @@
     }
 
     Ref<Element> constructElementWithFallback(Document&, const AtomicString&);
+    Ref<Element> constructElementWithFallback(Document&, const QualifiedName&);
 
     void upgradeElement(Element&);
 

Modified: trunk/Source/WebCore/dom/CustomElementRegistry.cpp (208715 => 208716)


--- trunk/Source/WebCore/dom/CustomElementRegistry.cpp	2016-11-15 00:19:24 UTC (rev 208715)
+++ trunk/Source/WebCore/dom/CustomElementRegistry.cpp	2016-11-15 00:27:01 UTC (rev 208716)
@@ -87,11 +87,9 @@
 
 JSCustomElementInterface* CustomElementRegistry::findInterface(const QualifiedName& name) const
 {
-    ASSERT(!name.hasPrefix());
     if (name.namespaceURI() != HTMLNames::xhtmlNamespaceURI)
         return nullptr;
-    auto it = m_nameMap.find(name.localName());
-    return it == m_nameMap.end() || it->value->name() != name ? nullptr : const_cast<JSCustomElementInterface*>(it->value.ptr());
+    return m_nameMap.get(name.localName());
 }
 
 JSCustomElementInterface* CustomElementRegistry::findInterface(const AtomicString& name) const

Modified: trunk/Source/WebCore/dom/DOMImplementation.cpp (208715 => 208716)


--- trunk/Source/WebCore/dom/DOMImplementation.cpp	2016-11-15 00:19:24 UTC (rev 208715)
+++ trunk/Source/WebCore/dom/DOMImplementation.cpp	2016-11-15 00:27:01 UTC (rev 208716)
@@ -114,6 +114,7 @@
 
     RefPtr<Element> documentElement;
     if (!qualifiedName.isEmpty()) {
+        ASSERT(!document->domWindow()); // If domWindow is not null, createElementNS could find CustomElementRegistry and arbitrary scripts.
         auto result = document->createElementNS(namespaceURI, qualifiedName);
         if (result.hasException())
             return result.releaseException();

Modified: trunk/Source/WebCore/dom/Document.cpp (208715 => 208716)


--- trunk/Source/WebCore/dom/Document.cpp	2016-11-15 00:19:24 UTC (rev 208715)
+++ trunk/Source/WebCore/dom/Document.cpp	2016-11-15 00:27:01 UTC (rev 208716)
@@ -861,22 +861,36 @@
     styleScope().clearResolver();
 }
 
-static ALWAYS_INLINE RefPtr<HTMLElement> createUpgradeCandidateElement(Document& document, const QualifiedName& name)
+static ALWAYS_INLINE Ref<HTMLElement> createUpgradeCandidateElement(Document& document, const QualifiedName& name)
 {
-    if (!RuntimeEnabledFeatures::sharedFeatures().customElementsEnabled())
-        return nullptr;
+    if (!RuntimeEnabledFeatures::sharedFeatures().customElementsEnabled()
+        || Document::validateCustomElementName(name.localName()) != CustomElementNameValidationStatus::Valid)
+        return HTMLUnknownElement::create(name, document);
 
-    if (Document::validateCustomElementName(name.localName()) != CustomElementNameValidationStatus::Valid)
-        return nullptr;
-
     auto element = HTMLElement::create(name, document);
     element->setIsCustomElementUpgradeCandidate();
-    return WTFMove(element);
+    return element;
 }
 
-static ExceptionOr<Ref<Element>> createHTMLElementWithNameValidation(Document& document, const AtomicString& localName)
+static ALWAYS_INLINE Ref<HTMLElement> createUpgradeCandidateElement(Document& document, const AtomicString& localName)
 {
-    auto element = HTMLElementFactory::createKnownElement(localName, document);
+    return createUpgradeCandidateElement(document, QualifiedName { nullAtom, localName, xhtmlNamespaceURI });
+}
+
+static inline bool isValidHTMLElementName(const AtomicString& localName)
+{
+    return Document::isValidName(localName);
+}
+
+static inline bool isValidHTMLElementName(const QualifiedName& name)
+{
+    return Document::isValidName(name.localName());
+}
+
+template<typename NameType>
+static ExceptionOr<Ref<Element>> createHTMLElementWithNameValidation(Document& document, const NameType& name)
+{
+    auto element = HTMLElementFactory::createKnownElement(name, document);
     if (LIKELY(element))
         return Ref<Element> { element.releaseNonNull() };
 
@@ -883,20 +897,15 @@
     if (auto* window = document.domWindow()) {
         auto* registry = window->customElementRegistry();
         if (UNLIKELY(registry)) {
-            if (auto* elementInterface = registry->findInterface(localName))
-                return elementInterface->constructElementWithFallback(document, localName);
+            if (auto* elementInterface = registry->findInterface(name))
+                return elementInterface->constructElementWithFallback(document, name);
         }
     }
 
-    if (UNLIKELY(!Document::isValidName(localName)))
+    if (UNLIKELY(!isValidHTMLElementName(name)))
         return Exception { INVALID_CHARACTER_ERR };
 
-    QualifiedName qualifiedName { nullAtom, localName, xhtmlNamespaceURI };
-
-    if (auto element = createUpgradeCandidateElement(document, qualifiedName))
-        return Ref<Element> { element.releaseNonNull() };
-
-    return Ref<Element> { HTMLUnknownElement::create(qualifiedName, document) };
+    return Ref<Element> { createUpgradeCandidateElement(document, name) };
 }
 
 ExceptionOr<Ref<Element>> Document::createElementForBindings(const AtomicString& name)
@@ -1055,9 +1064,7 @@
         }
     }
     // FIXME: Should we also check the equality of prefix between the custom element and name?
-    if (auto element = createUpgradeCandidateElement(document, name))
-        return element.releaseNonNull();
-    return HTMLUnknownElement::create(name, document);
+    return createUpgradeCandidateElement(document, name);
 }
 
 // FIXME: This should really be in a possible ElementFactory class.
@@ -1159,6 +1166,10 @@
     QualifiedName parsedName { parseResult.releaseReturnValue() };
     if (!hasValidNamespaceForElements(parsedName))
         return Exception { NAMESPACE_ERR };
+
+    if (parsedName.namespaceURI() == xhtmlNamespaceURI)
+        return createHTMLElementWithNameValidation(*this, parsedName);
+
     return createElement(parsedName, false);
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to