Make HTML parser construct custom elements
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 2 Mar 2016 21:56:47 +0000 (21:56 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 2 Mar 2016 21:56:47 +0000 (21:56 +0000)
https://bugs.webkit.org/show_bug.cgi?id=154908
Source/WebCore:

<rdar://problem/24923735>

Reviewed by Antti Koivisto.

Added the support for instantiating custom elements inside the parser. Based on Jan F2F discussion,
the HTML parser is going to synchronously construct custom elements. When a custom element constructor
throws, the HTML parser creates a HTMLUnknownElement instead.

In our implementation, we pause the parser completely and construct custom elements using the same
mechanism used to run author scripts. It's possible that we may want to apply some optimizations to
to make custom element construction but it's probably a good idea to get semantics right first.

Tests: fast/custom-elements/parser/parser-constructs-custom-elements.html
       fast/custom-elements/parser/parser-fallsback-to-unknown-element.html
       fast/custom-elements/parser/parser-sets-attributes-and-children.html
       fast/custom-elements/parser/parser-uses-constructed-element.html

* bindings/js/JSCustomElementInterface.cpp:
(WebCore::JSCustomElementInterface::constructElement): Added ShouldClearException as an argument
to be used by the HTML parser since the parser can't re-throw to anywhere or fail parsing.

* bindings/js/JSCustomElementInterface.h:
(WebCore::JSCustomElementInterface::ShouldClearException): Added.

* dom/Document.cpp:
(WebCore::createHTMLElementWithNameValidation): Do not clear the exception here since createElement
must re-throw the exception thrown by a custom element constructor.
(WebCore::Document::createElementForBindings):

* dom/make_names.pl:
(printFactoryCppFile): Added ConstructorFunctionMapEntry which contains the constructor function
as well as the qualified name.
(printFactoryHeaderFile): Added a variant of createKnownElement and createElement that takes
AtomicString instead of QualifiedName.

* html/parser/HTMLConstructionSite.cpp:
(WebCore::setAttributes): Added a variant that takes Vector<Attribute>.
(WebCore::HTMLConstructionSite::insertHTMLElementOrFindCustomElementInterface): Added. Returns a
custom element interface when the element doesn't match any builtin element and there is a custom
element definition that matches the specified name.
(WebCore::HTMLConstructionSite::insertCustomElement): Added. Like insertElement but also sets the
attributes on the newly created custom element.
(WebCore::HTMLConstructionSite::createHTMLElementOrFindCustomElementInterface): Extracted from
createHTMLElement. When customElementInterface is not nullptr, we optionally find the custom
element interface and return nullptr.
(WebCore::HTMLConstructionSite::createHTMLElement):
* html/parser/HTMLConstructionSite.h:

* html/parser/HTMLDocumentParser.cpp:
(WebCore::HTMLDocumentParser::runScriptsForPausedTreeBuilder): Create a custom element when there
is a pending custom element to create (i.e. m_customElementToConstruct is not empty).
(WebCore::HTMLDocumentParser::isWaitingForScripts):

* html/parser/HTMLStackItem.h:
(WebCore::HTMLStackItem::create): Added a variant used for custom elements.
(WebCore::HTMLStackItem::HTMLStackItem): Ditto.

* html/parser/HTMLTreeBuilder.cpp:
(WebCore::CustomElementConstructionData::CustomElementConstructionData): Added. It needs to be in
the cpp file to avoid introducing more header dependencies in HTMLTreeBuilder.h.
(WebCore::CustomElementConstructionData::~CustomElementConstructionData): Ditto.
(WebCore::HTMLTreeBuilder::processStartTagForInBody): Use insertGenericHTMLElement when creating
a generic element that could be custom elements.
(WebCore::HTMLTreeBuilder::insertGenericHTMLElement): Added. Create and insert a new element
or set m_customElementToConstruct so that the HTMLDocumentParser will create a custom element later.
(WebCore::HTMLTreeBuilder::didCreateCustomOrCallbackElement): Added. Called by HTMLDocumentParser
when it finishes creating a new custom element.

* html/parser/HTMLTreeBuilder.h:
(WebCore::HTMLTreeBuilder::takeCustomElementConstructionData): Added.
(WebCore::HTMLTreeBuilder::hasParserBlockingScriptWork): Renamed from hasParserBlockingScript.
Checks the existence of m_customElementToConstruct as well as m_scriptToProcess.

LayoutTests:

Reviewed by Antti Koivisto.

Added W3C testharness.js based tests for instantiating custom elements inside the HTML parser.

* fast/custom-elements/parser: Added.
* fast/custom-elements/parser/parser-constructs-custom-elements-expected.txt: Added.
* fast/custom-elements/parser/parser-constructs-custom-elements.html: Added.
* fast/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt: Added.
* fast/custom-elements/parser/parser-fallsback-to-unknown-element.html: Added.
* fast/custom-elements/parser/parser-sets-attributes-and-children-expected.txt: Added.
* fast/custom-elements/parser/parser-sets-attributes-and-children.html: Added.
* fast/custom-elements/parser/parser-uses-constructed-element-expected.txt: Added.
* fast/custom-elements/parser/parser-uses-constructed-element.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@197463 268f45cc-cd09-0410-ab3c-d52691b4dbfc

20 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/custom-elements/parser/parser-constructs-custom-elements-expected.txt [new file with mode: 0644]
LayoutTests/fast/custom-elements/parser/parser-constructs-custom-elements.html [new file with mode: 0644]
LayoutTests/fast/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt [new file with mode: 0644]
LayoutTests/fast/custom-elements/parser/parser-fallsback-to-unknown-element.html [new file with mode: 0644]
LayoutTests/fast/custom-elements/parser/parser-sets-attributes-and-children-expected.txt [new file with mode: 0644]
LayoutTests/fast/custom-elements/parser/parser-sets-attributes-and-children.html [new file with mode: 0644]
LayoutTests/fast/custom-elements/parser/parser-uses-constructed-element-expected.txt [new file with mode: 0644]
LayoutTests/fast/custom-elements/parser/parser-uses-constructed-element.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/JSCustomElementInterface.cpp
Source/WebCore/bindings/js/JSCustomElementInterface.h
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/make_names.pl
Source/WebCore/html/parser/HTMLConstructionSite.cpp
Source/WebCore/html/parser/HTMLConstructionSite.h
Source/WebCore/html/parser/HTMLDocumentParser.cpp
Source/WebCore/html/parser/HTMLStackItem.h
Source/WebCore/html/parser/HTMLTreeBuilder.cpp
Source/WebCore/html/parser/HTMLTreeBuilder.h

index 6d26695..67a3ae6 100644 (file)
@@ -1,3 +1,22 @@
+2016-03-01  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Make HTML parser construct custom elements
+        https://bugs.webkit.org/show_bug.cgi?id=154908
+
+        Reviewed by Antti Koivisto.
+
+        Added W3C testharness.js based tests for instantiating custom elements inside the HTML parser.
+
+        * fast/custom-elements/parser: Added.
+        * fast/custom-elements/parser/parser-constructs-custom-elements-expected.txt: Added.
+        * fast/custom-elements/parser/parser-constructs-custom-elements.html: Added.
+        * fast/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt: Added.
+        * fast/custom-elements/parser/parser-fallsback-to-unknown-element.html: Added.
+        * fast/custom-elements/parser/parser-sets-attributes-and-children-expected.txt: Added.
+        * fast/custom-elements/parser/parser-sets-attributes-and-children.html: Added.
+        * fast/custom-elements/parser/parser-uses-constructed-element-expected.txt: Added.
+        * fast/custom-elements/parser/parser-uses-constructed-element.html: Added.
+
 2016-03-02  Chris Dumez  <cdumez@apple.com>
 
         Align HTMLInputElement.maxLength with the specification
diff --git a/LayoutTests/fast/custom-elements/parser/parser-constructs-custom-elements-expected.txt b/LayoutTests/fast/custom-elements/parser/parser-constructs-custom-elements-expected.txt
new file mode 100644 (file)
index 0000000..89e28a3
--- /dev/null
@@ -0,0 +1,4 @@
+
+PASS HTML parser must NOT create a custom element before defineElement is called 
+PASS HTML parser must create a defined custom element before executing inline scripts 
+
diff --git a/LayoutTests/fast/custom-elements/parser/parser-constructs-custom-elements.html b/LayoutTests/fast/custom-elements/parser/parser-constructs-custom-elements.html
new file mode 100644 (file)
index 0000000..24db95d
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: Changes to the HTML parser</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="HTML parser creates a custom element">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<link rel='stylesheet' href='../../../resources/testharness.css'>
+</head>
+<body>
+<div id="log"></div>
+<my-custom-element id="instance1"></my-custom-element>
+<script>
+
+class MyCustomElement extends HTMLElement { };
+
+test(function () {
+    var customElement = document.getElementById('instance1');
+
+    assert_true(customElement instanceof HTMLElement, 'An unresolved custom element must be an instance of HTMLElement');
+    assert_false(customElement instanceof MyCustomElement, 'An unresolved custom element must NOT be an instance of that custom element');
+    assert_equals(customElement.localName, 'my-custom-element');
+    assert_equals(customElement.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom element HTML must use HTML namespace');
+
+}, 'HTML parser must NOT create a custom element before defineElement is called');
+
+document.defineCustomElement('my-custom-element', MyCustomElement);
+
+</script>
+<my-custom-element id="instance2"></my-custom-element>
+<script>
+
+test(function () {
+    var customElement = document.getElementById('instance2');
+
+    assert_true(customElement instanceof HTMLElement, 'A resolved custom element must be an instance of HTMLElement');
+    assert_false(customElement instanceof HTMLUnknownElement, 'A resolved custom element must NOT be an instance of HTMLUnknownElement');
+    assert_true(customElement instanceof MyCustomElement, 'A resolved custom element must be an instance of that custom element');
+    assert_equals(customElement.localName, 'my-custom-element');
+    assert_equals(customElement.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom element HTML must use HTML namespace');
+
+}, 'HTML parser must create a defined custom element before executing inline scripts');
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt b/LayoutTests/fast/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt
new file mode 100644 (file)
index 0000000..293cb4d
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS HTML parser must create a fallback HTMLUnknownElement when a custom element constructor returns a Text node 
+PASS HTML parser must create a fallback HTMLUnknownElement when a custom element constructor returns non-Element object 
+PASS HTML parser must create a fallback HTMLUnknownElement when a custom element constructor does not call super() 
+PASS HTML parser must create a fallback HTMLUnknownElement when a custom element constructor throws an exception 
+
diff --git a/LayoutTests/fast/custom-elements/parser/parser-fallsback-to-unknown-element.html b/LayoutTests/fast/custom-elements/parser/parser-fallsback-to-unknown-element.html
new file mode 100644 (file)
index 0000000..ab1cbb2
--- /dev/null
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: Changes to the HTML parser</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="HTML parser must fallback to creating a HTMLUnknownElement when a custom element construction fails">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<link rel='stylesheet' href='../../../resources/testharness.css'>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+class ReturnsTextNode extends HTMLElement {
+    constructor() {
+        super();
+        return document.createTextNode('some text');
+    }
+};
+document.defineCustomElement('returns-text', ReturnsTextNode);
+
+class ReturnsNonElementObject extends HTMLElement {
+    constructor() {
+        super();
+        return {};
+    }
+};
+document.defineCustomElement('returns-non-element-object', ReturnsNonElementObject);
+
+class LacksSuperCall extends HTMLElement {
+    constructor() { }
+};
+document.defineCustomElement('lacks-super-call', LacksSuperCall);
+
+class ThrowsException extends HTMLElement {
+    constructor() {
+        throw 'Bad';
+    }
+};
+document.defineCustomElement('throws-exception', ThrowsException);
+
+</script>
+<returns-text></returns-text>
+<returns-non-element-object></returns-non-element-object>
+<lacks-super-call></lacks-super-call>
+<throws-exception></throws-exception>
+<script>
+
+test(function () {
+    var instance = document.querySelector('returns-text');
+
+    assert_false(instance instanceof ReturnsTextNode, 'HTML parser must NOT instantiate a custom element when the constructor returns a Text node');
+    assert_true(instance instanceof HTMLElement, 'The fallback element created by HTML parser must be an instance of HTMLElement');
+    assert_true(instance instanceof HTMLUnknownElement, 'The fallback element created by HTML parser must be an instance of HTMLUnknownElement');
+
+}, 'HTML parser must create a fallback HTMLUnknownElement when a custom element constructor returns a Text node');
+
+test(function () {
+    var instance = document.querySelector('returns-non-element-object');
+
+    assert_false(instance instanceof ReturnsNonElementObject, 'HTML parser must NOT instantiate a custom element when the constructor returns a non-Element object');
+    assert_true(instance instanceof HTMLElement, 'The fallback element created by HTML parser must be an instance of HTMLElement');
+    assert_true(instance instanceof HTMLUnknownElement, 'The fallback element created by HTML parser must be an instance of HTMLUnknownElement');
+
+}, 'HTML parser must create a fallback HTMLUnknownElement when a custom element constructor returns non-Element object');
+
+test(function () {
+    var instance = document.querySelector('lacks-super-call');
+
+    assert_false(instance instanceof LacksSuperCall, 'HTML parser must NOT instantiate a custom element when the constructor does not call super()');
+    assert_true(instance instanceof HTMLElement, 'The fallback element created by HTML parser must be an instance of HTMLElement');
+    assert_true(instance instanceof HTMLUnknownElement, 'The fallback element created by HTML parser must be an instance of HTMLUnknownElement');
+
+}, 'HTML parser must create a fallback HTMLUnknownElement when a custom element constructor does not call super()');
+
+test(function () {
+    var instance = document.querySelector('throws-exception');
+
+    assert_false(instance instanceof ThrowsException, 'HTML parser must NOT instantiate a custom element when the constructor throws an exception');
+    assert_true(instance instanceof HTMLElement, 'The fallback element created by HTML parser must be an instance of HTMLElement');
+    assert_true(instance instanceof HTMLUnknownElement, 'The fallback element created by HTML parser must be an instance of HTMLUnknownElement');
+
+}, 'HTML parser must create a fallback HTMLUnknownElement when a custom element constructor throws an exception');
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/custom-elements/parser/parser-sets-attributes-and-children-expected.txt b/LayoutTests/fast/custom-elements/parser/parser-sets-attributes-and-children-expected.txt
new file mode 100644 (file)
index 0000000..71f6ea1
--- /dev/null
@@ -0,0 +1,6 @@
+hello world
+
+PASS HTML parser must set the attributes 
+PASS HTML parser must append child nodes 
+PASS HTML parser must set the attributes or append children before calling constructor 
+
diff --git a/LayoutTests/fast/custom-elements/parser/parser-sets-attributes-and-children.html b/LayoutTests/fast/custom-elements/parser/parser-sets-attributes-and-children.html
new file mode 100644 (file)
index 0000000..8249512
--- /dev/null
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: Changes to the HTML parser</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="HTML parser must set the attributes and append the children on a custom element">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<link rel='stylesheet' href='../../../resources/testharness.css'>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+var numberOfAttributesInConstructor;
+var numberOfChildNodesInConstructor;
+
+class MyCustomElement extends HTMLElement {
+    constructor(...args) {
+        super(...args);
+        numberOfAttributesInConstructor = this.attributes.length;
+        numberOfChildNodesInConstructor = this.childNodes.length;
+    }
+};
+document.defineCustomElement('my-custom-element', MyCustomElement);
+
+</script>
+<my-custom-element id="custom-element-id" class="class1 class2">hello <b>world</b></my-custom-element>
+<script>
+
+var customElement = document.querySelector('my-custom-element');
+
+test(function () {
+    assert_equals(customElement.getAttribute('id'), 'custom-element-id', 'HTML parser must preserve the id attribute');
+    assert_equals(customElement.id, 'custom-element-id', 'HTML parser must preserve the semantics of reflect for the id attribute');
+    assert_equals(customElement.getAttribute('class'), 'class1 class2', 'HTML parser must preserve the class attribute');
+    assert_equals(customElement.classList.length, 2, 'HTML parser must initialize classList on custom elements');
+    assert_equals(customElement.classList[0], 'class1', 'HTML parser must initialize classList on custom elements');
+    assert_equals(customElement.classList[1], 'class2', 'HTML parser must initialize classList on custom elements');
+
+    assert_equals(customElement.childNodes.length, 2, 'HTML parser must append child nodes');
+    assert_equals(customElement.classList[0], 'class1', 'HTML parser must initialize classList on custom elements');
+    assert_equals(customElement.classList[1], 'class2', 'HTML parser must initialize classList on custom elements');
+
+}, 'HTML parser must set the attributes');
+
+test(function () {
+    assert_equals(customElement.childNodes.length, 2, 'HTML parser must append child nodes');
+    assert_true(customElement.firstChild instanceof Text, 'HTML parser must append Text node child to a custom element');
+    assert_equals(customElement.firstChild.data, 'hello ', 'HTML parser must append Text node child to a custom element');
+    assert_true(customElement.lastChild instanceof HTMLElement, 'HTML parser must append a builtin element child to a custom element');
+    assert_true(customElement.lastChild.firstChild instanceof Text, 'HTML parser must preserve grandchild nodes of a custom element');
+    assert_equals(customElement.lastChild.firstChild.data, 'world', 'HTML parser must preserve grandchild nodes of a custom element');
+}, 'HTML parser must append child nodes');
+
+test(function () {
+    assert_equals(numberOfAttributesInConstructor, 0, 'HTML parser must not set attributes on a custom element before invoking the constructor');
+    assert_equals(numberOfChildNodesInConstructor, 0, 'HTML parser must not append child nodes to a custom element before invoking the constructor');
+}, 'HTML parser must set the attributes or append children before calling constructor');
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/custom-elements/parser/parser-uses-constructed-element-expected.txt b/LayoutTests/fast/custom-elements/parser/parser-uses-constructed-element-expected.txt
new file mode 100644 (file)
index 0000000..e052f35
--- /dev/null
@@ -0,0 +1,4 @@
+
+PASS HTML parser must use the returned value of the custom element constructor instead of the one created before super() call 
+PASS HTML parser must use the returned value of the custom element constructor instead using the one created in super() call 
+
diff --git a/LayoutTests/fast/custom-elements/parser/parser-uses-constructed-element.html b/LayoutTests/fast/custom-elements/parser/parser-uses-constructed-element.html
new file mode 100644 (file)
index 0000000..76a1216
--- /dev/null
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: Changes to the HTML parser</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="HTML parser must construct a custom element instead of upgrading">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<link rel='stylesheet' href='../../../resources/testharness.css'>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+let anotherElementCreatedBeforeSuperCall = undefined;
+let elementCreatedBySuperCall = undefined;
+let shouldCreateElementBeforeSuperCall = true;
+class InstantiatesItselfBeforeSuper extends HTMLElement {
+    constructor() {
+        if (shouldCreateElementBeforeSuperCall) {
+            shouldCreateElementBeforeSuperCall = false;
+            anotherElementCreatedBeforeSuperCall = new InstantiatesItselfBeforeSuper();
+        }
+        super();
+        elementCreatedBySuperCall = this;
+    }
+};
+document.defineCustomElement('instantiates-itself-before-super', InstantiatesItselfBeforeSuper);
+
+let shouldCreateAnotherInstance = true;
+let anotherInstance = undefined;
+let firstInstance = undefined;
+class ReturnsAnotherInstance extends HTMLElement {
+    constructor() {
+        super();
+        if (shouldCreateAnotherInstance) {
+            shouldCreateAnotherInstance = false;
+            firstInstance = this;
+            anotherInstance = new ReturnsAnotherInstance;
+            return anotherInstance;
+        } else
+            return this;
+    }
+};
+document.defineCustomElement('returns-another-instance', ReturnsAnotherInstance);
+
+</script>
+<instantiates-itself-before-super></instantiates-itself-before-super>
+<returns-another-instance></returns-another-instance>
+<script>
+
+test(function () {
+    var instance = document.querySelector('instantiates-itself-before-super');
+
+    assert_equals(instance, elementCreatedBySuperCall, 'HTML parser must insert the element returned by the custom element constructor');
+    assert_not_equals(instance, anotherElementCreatedBeforeSuperCall, 'HTML parser must not insert another instance of the custom element created before super() call');
+    assert_equals(anotherElementCreatedBeforeSuperCall.parentNode, null, 'HTML parser must not insert another instance of the custom element created before super() call');
+
+}, 'HTML parser must use the returned value of the custom element constructor instead of the one created before super() call');
+
+test(function () {
+    var instance = document.querySelector('returns-another-instance');
+
+    assert_equals(instance, anotherInstance, 'HTML parser must insert the element returned by the custom element constructor');
+    assert_not_equals(instance, firstInstance, 'HTML parser must not insert the element created by super() call if the constructor returned another element');
+    assert_equals(firstInstance.parentNode, null, 'HTML parser must not insert the element created by super() call if the constructor returned another element');
+
+}, 'HTML parser must use the returned value of the custom element constructor instead using the one created in super() call');
+
+</script>
+</body>
+</html>
index 2968757..eb61f19 100644 (file)
@@ -1,3 +1,80 @@
+2016-03-01  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Make HTML parser construct custom elements
+        https://bugs.webkit.org/show_bug.cgi?id=154908
+        <rdar://problem/24923735>
+
+        Reviewed by Antti Koivisto.
+
+        Added the support for instantiating custom elements inside the parser. Based on Jan F2F discussion,
+        the HTML parser is going to synchronously construct custom elements. When a custom element constructor
+        throws, the HTML parser creates a HTMLUnknownElement instead.
+
+        In our implementation, we pause the parser completely and construct custom elements using the same
+        mechanism used to run author scripts. It's possible that we may want to apply some optimizations to
+        to make custom element construction but it's probably a good idea to get semantics right first.
+
+        Tests: fast/custom-elements/parser/parser-constructs-custom-elements.html
+               fast/custom-elements/parser/parser-fallsback-to-unknown-element.html
+               fast/custom-elements/parser/parser-sets-attributes-and-children.html
+               fast/custom-elements/parser/parser-uses-constructed-element.html
+
+        * bindings/js/JSCustomElementInterface.cpp:
+        (WebCore::JSCustomElementInterface::constructElement): Added ShouldClearException as an argument
+        to be used by the HTML parser since the parser can't re-throw to anywhere or fail parsing.
+
+        * bindings/js/JSCustomElementInterface.h:
+        (WebCore::JSCustomElementInterface::ShouldClearException): Added.
+
+        * dom/Document.cpp:
+        (WebCore::createHTMLElementWithNameValidation): Do not clear the exception here since createElement
+        must re-throw the exception thrown by a custom element constructor.
+        (WebCore::Document::createElementForBindings):
+
+        * dom/make_names.pl:
+        (printFactoryCppFile): Added ConstructorFunctionMapEntry which contains the constructor function
+        as well as the qualified name.
+        (printFactoryHeaderFile): Added a variant of createKnownElement and createElement that takes
+        AtomicString instead of QualifiedName.
+
+        * html/parser/HTMLConstructionSite.cpp:
+        (WebCore::setAttributes): Added a variant that takes Vector<Attribute>.
+        (WebCore::HTMLConstructionSite::insertHTMLElementOrFindCustomElementInterface): Added. Returns a
+        custom element interface when the element doesn't match any builtin element and there is a custom
+        element definition that matches the specified name.
+        (WebCore::HTMLConstructionSite::insertCustomElement): Added. Like insertElement but also sets the
+        attributes on the newly created custom element.
+        (WebCore::HTMLConstructionSite::createHTMLElementOrFindCustomElementInterface): Extracted from
+        createHTMLElement. When customElementInterface is not nullptr, we optionally find the custom
+        element interface and return nullptr.
+        (WebCore::HTMLConstructionSite::createHTMLElement):
+        * html/parser/HTMLConstructionSite.h:
+
+        * html/parser/HTMLDocumentParser.cpp:
+        (WebCore::HTMLDocumentParser::runScriptsForPausedTreeBuilder): Create a custom element when there
+        is a pending custom element to create (i.e. m_customElementToConstruct is not empty).
+        (WebCore::HTMLDocumentParser::isWaitingForScripts):
+
+        * html/parser/HTMLStackItem.h:
+        (WebCore::HTMLStackItem::create): Added a variant used for custom elements.
+        (WebCore::HTMLStackItem::HTMLStackItem): Ditto.
+
+        * html/parser/HTMLTreeBuilder.cpp:
+        (WebCore::CustomElementConstructionData::CustomElementConstructionData): Added. It needs to be in
+        the cpp file to avoid introducing more header dependencies in HTMLTreeBuilder.h.
+        (WebCore::CustomElementConstructionData::~CustomElementConstructionData): Ditto.
+        (WebCore::HTMLTreeBuilder::processStartTagForInBody): Use insertGenericHTMLElement when creating
+        a generic element that could be custom elements.
+        (WebCore::HTMLTreeBuilder::insertGenericHTMLElement): Added. Create and insert a new element
+        or set m_customElementToConstruct so that the HTMLDocumentParser will create a custom element later.
+        (WebCore::HTMLTreeBuilder::didCreateCustomOrCallbackElement): Added. Called by HTMLDocumentParser
+        when it finishes creating a new custom element.
+
+        * html/parser/HTMLTreeBuilder.h:
+        (WebCore::HTMLTreeBuilder::takeCustomElementConstructionData): Added.
+        (WebCore::HTMLTreeBuilder::hasParserBlockingScriptWork): Renamed from hasParserBlockingScript.
+        Checks the existence of m_customElementToConstruct as well as m_scriptToProcess.
+
 2016-03-02  Zalan Bujtas  <zalan@apple.com>
 
         Use IndentTextOrNot instead of passing isFirstLine/shouldIndentText as bool.
index 3a0013b..ebeb565 100644 (file)
@@ -55,7 +55,7 @@ JSCustomElementInterface::~JSCustomElementInterface()
 {
 }
 
-RefPtr<Element> JSCustomElementInterface::constructElement(const AtomicString& tagName)
+RefPtr<Element> JSCustomElementInterface::constructElement(const AtomicString& tagName, ShouldClearException shouldClearException)
 {
     if (!canInvokeCallback())
         return nullptr;
@@ -88,6 +88,9 @@ RefPtr<Element> JSCustomElementInterface::constructElement(const AtomicString& t
     JSValue newElement = construct(state, m_constructor.get(), constructType, constructData, args);
     InspectorInstrumentation::didCallFunction(cookie, context);
 
+    if (shouldClearException == ShouldClearException::Clear && state->hadException())
+        state->clearException();
+
     if (newElement.isEmpty())
         return nullptr;
 
index 6e4c9ed..53bc23d 100644 (file)
@@ -59,7 +59,8 @@ public:
         return adoptRef(*new JSCustomElementInterface(callback, globalObject));
     }
 
-    RefPtr<Element> constructElement(const AtomicString&);
+    enum class ShouldClearException { Clear, DoNotClear };
+    RefPtr<Element> constructElement(const AtomicString&, ShouldClearException);
 
     ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); }
     JSC::JSObject* constructor() { return m_constructor.get(); }
index e2ae566..d10eee1 100644 (file)
@@ -879,35 +879,35 @@ void Document::childrenChanged(const ChildChange& change)
     clearStyleResolver();
 }
 
-static RefPtr<Element> createHTMLElementWithNameValidation(Document& document, const QualifiedName qualifiedName, ExceptionCode& ec)
+static RefPtr<Element> createHTMLElementWithNameValidation(Document& document, const AtomicString& localName, ExceptionCode& ec)
 {
-    RefPtr<HTMLElement> element = HTMLElementFactory::createKnownElement(qualifiedName, document);
+    RefPtr<HTMLElement> element = HTMLElementFactory::createKnownElement(localName, document);
     if (LIKELY(element))
         return element;
 
 #if ENABLE(CUSTOM_ELEMENTS)
     auto* definitions = document.customElementDefinitions();
     if (UNLIKELY(definitions)) {
-        if (auto* interface = definitions->findInterface(qualifiedName))
-            return interface->constructElement(qualifiedName.localName());
+        if (auto* interface = definitions->findInterface(localName))
+            return interface->constructElement(localName, JSCustomElementInterface::ShouldClearException::DoNotClear);
     }
 #endif
 
-    if (UNLIKELY(!Document::isValidName(qualifiedName.localName()))) {
+    if (UNLIKELY(!Document::isValidName(localName))) {
         ec = INVALID_CHARACTER_ERR;
         return nullptr;
     }
 
-    return HTMLUnknownElement::create(qualifiedName, document);
+    return HTMLUnknownElement::create(QualifiedName(nullAtom, localName, xhtmlNamespaceURI), document);
 }
 
 RefPtr<Element> Document::createElementForBindings(const AtomicString& name, ExceptionCode& ec)
 {
     if (isHTMLDocument())
-        return createHTMLElementWithNameValidation(*this, QualifiedName(nullAtom, name.convertToASCIILowercase(), xhtmlNamespaceURI), ec);
+        return createHTMLElementWithNameValidation(*this, name.convertToASCIILowercase(), ec);
 
     if (isXHTMLDocument())
-        return createHTMLElementWithNameValidation(*this, QualifiedName(nullAtom, name, xhtmlNamespaceURI), ec);
+        return createHTMLElementWithNameValidation(*this, name, ec);
 
     if (!isValidName(name)) {
         ec = INVALID_CHARACTER_ERR;
index 8c2a2d2..1a070f7 100755 (executable)
@@ -986,10 +986,28 @@ END
         $argumentList = "name, document, createdByParser";
     }
 
+    my $lowercaseNamespacePrefix = lc($parameters{namespacePrefix});
+
     printConstructors($F, \%tagConstructorMap);
 
     print F <<END
-static NEVER_INLINE void populate$parameters{namespace}FactoryMap(HashMap<AtomicStringImpl*, $parameters{namespace}ConstructorFunction>& map)
+
+struct ConstructorFunctionMapEntry {
+    ConstructorFunctionMapEntry($parameters{namespace}ConstructorFunction function, const QualifiedName& name)
+        : function(function)
+        , qualifiedName(&name)
+    { }
+
+    ConstructorFunctionMapEntry()
+        : function(nullptr)
+        , qualifiedName(nullptr)
+    { }
+
+    $parameters{namespace}ConstructorFunction function;
+    const QualifiedName* qualifiedName; // Use pointer instead of reference so that emptyValue() in HashMap is cheap to create.
+};
+
+static NEVER_INLINE void populate$parameters{namespace}FactoryMap(HashMap<AtomicStringImpl*, ConstructorFunctionMapEntry>& map)
 {
     struct TableEntry {
         const QualifiedName& name;
@@ -1006,25 +1024,42 @@ END
     };
 
     for (unsigned i = 0; i < WTF_ARRAY_LENGTH(table); ++i)
-        map.add(table[i].name.localName().impl(), table[i].function);
+        map.add(table[i].name.localName().impl(), ConstructorFunctionMapEntry(table[i].function, table[i].name));
 }
 
-RefPtr<$parameters{namespace}Element> $parameters{namespace}ElementFactory::createKnownElement(const QualifiedName& name, Document& document$formElementArgumentForDefinition, bool createdByParser)
+
+static ConstructorFunctionMapEntry find$parameters{namespace}ElementConstructorFunction(const AtomicString& localName)
 {
-    static NeverDestroyed<HashMap<AtomicStringImpl*, $parameters{namespace}ConstructorFunction>> functions;
-    if (functions.get().isEmpty())
-        populate$parameters{namespace}FactoryMap(functions);
-    $parameters{namespace}ConstructorFunction function = functions.get().get(name.localName().impl());
-    if (LIKELY(function))
-        return function($argumentList);
+    static NeverDestroyed<HashMap<AtomicStringImpl*, ConstructorFunctionMapEntry>> map;
+    if (map.get().isEmpty())
+        populate$parameters{namespace}FactoryMap(map);
+    return map.get().get(localName.impl());
+}
+
+RefPtr<$parameters{namespace}Element> $parameters{namespace}ElementFactory::createKnownElement(const AtomicString& localName, Document& document$formElementArgumentForDefinition, bool createdByParser)
+{
+    const ConstructorFunctionMapEntry& entry = find$parameters{namespace}ElementConstructorFunction(localName);
+    if (LIKELY(entry.function)) {
+        ASSERT(entry.qualifiedName);
+        const auto& name = *entry.qualifiedName;
+        return entry.function($argumentList);
+    }
     return nullptr;
 }
 
-Ref<$parameters{namespace}Element> $parameters{namespace}ElementFactory::createElement(const QualifiedName& name, Document& document$formElementArgumentForDefinition, bool createdByParser)
+Ref<$parameters{namespace}Element> $parameters{namespace}ElementFactory::createElement(const AtomicString& name, Document& document$formElementArgumentForDefinition, bool createdByParser)
 {
     RefPtr<$parameters{namespace}Element> element = $parameters{namespace}ElementFactory::createKnownElement($argumentList);
     if (LIKELY(element))
         return element.releaseNonNull();
+    return $parameters{fallbackInterfaceName}::create(QualifiedName(nullAtom, name, ${lowercaseNamespacePrefix}NamespaceURI), document);
+}
+
+Ref<$parameters{namespace}Element> $parameters{namespace}ElementFactory::createElement(const QualifiedName& name, Document& document$formElementArgumentForDefinition, bool createdByParser)
+{
+    const ConstructorFunctionMapEntry& entry = find$parameters{namespace}ElementConstructorFunction(name.localName());
+    if (LIKELY(entry.function))
+        return entry.function($argumentList);
     return $parameters{fallbackInterfaceName}::create(name, document);
 }
 
@@ -1065,10 +1100,13 @@ namespace WebCore {
 END
 ;
 
-print F "        static RefPtr<$parameters{namespace}Element> createKnownElement(const QualifiedName&, Document&";
+print F "        static RefPtr<$parameters{namespace}Element> createKnownElement(const AtomicString& localName, Document&";
 print F ", HTMLFormElement* = nullptr" if $parameters{namespace} eq "HTML";
 print F ", bool createdByParser = false);\n\n";
-print F "        static Ref<$parameters{namespace}Element> createElement(const QualifiedName&, Document&";
+print F "        static Ref<$parameters{namespace}Element> createElement(const AtomicString& localName, Document&";
+print F ", HTMLFormElement* = nullptr" if $parameters{namespace} eq "HTML";
+print F ", bool createdByParser = false);\n";
+print F "        static Ref<$parameters{namespace}Element> createElement(const QualifiedName& localName, Document&";
 print F ", HTMLFormElement* = nullptr" if $parameters{namespace} eq "HTML";
 print F ", bool createdByParser = false);\n";
 
index 66cbaf3..dbb6ab8 100644 (file)
@@ -28,6 +28,7 @@
 #include "HTMLTreeBuilder.h"
 
 #include "Comment.h"
+#include "CustomElementDefinitions.h"
 #include "DocumentFragment.h"
 #include "DocumentType.h"
 #include "Frame.h"
@@ -43,6 +44,7 @@
 #include "HTMLPictureElement.h"
 #include "HTMLScriptElement.h"
 #include "HTMLTemplateElement.h"
+#include "HTMLUnknownElement.h"
 #include "NotImplemented.h"
 #include "SVGElement.h"
 #include "Text.h"
@@ -51,11 +53,16 @@ namespace WebCore {
 
 using namespace HTMLNames;
 
-static inline void setAttributes(Element& element, AtomicHTMLToken* token, ParserContentPolicy parserContentPolicy)
+static inline void setAttributes(Element& element, Vector<Attribute>& attributes, ParserContentPolicy parserContentPolicy)
 {
     if (!scriptingContentIsAllowed(parserContentPolicy))
-        element.stripScriptingAttributes(token->attributes());
-    element.parserSetAttributes(token->attributes());
+        element.stripScriptingAttributes(attributes);
+    element.parserSetAttributes(attributes);
+}
+
+static inline void setAttributes(Element& element, AtomicHTMLToken* token, ParserContentPolicy parserContentPolicy)
+{
+    setAttributes(element, token->attributes(), parserContentPolicy);
 }
 
 static bool hasImpliedEndTag(const HTMLStackItem& item)
@@ -482,6 +489,26 @@ void HTMLConstructionSite::insertHTMLElement(AtomicHTMLToken* token)
     m_openElements.push(HTMLStackItem::create(element.releaseNonNull(), *token));
 }
 
+#if ENABLE(CUSTOM_ELEMENTS)
+JSCustomElementInterface* HTMLConstructionSite::insertHTMLElementOrFindCustomElementInterface(AtomicHTMLToken* token)
+{
+    JSCustomElementInterface* interface = nullptr;
+    RefPtr<Element> element = createHTMLElementOrFindCustomElementInterface(token, &interface);
+    if (UNLIKELY(interface))
+        return interface;
+    attachLater(&currentNode(), element);
+    m_openElements.push(HTMLStackItem::create(element.releaseNonNull(), *token));
+    return nullptr;
+}
+
+void HTMLConstructionSite::insertCustomElement(Ref<Element>&& element, const AtomicString& localName, Vector<Attribute>& attributes)
+{
+    setAttributes(element.get(), attributes, m_parserContentPolicy);
+    attachLater(&currentNode(), element.ptr());
+    m_openElements.push(HTMLStackItem::create(WTFMove(element), localName, attributes));
+}
+#endif
+
 void HTMLConstructionSite::insertSelfClosingHTMLElement(AtomicHTMLToken* token)
 {
     ASSERT(token->type() == HTMLToken::StartTag);
@@ -633,28 +660,53 @@ inline Document& HTMLConstructionSite::ownerDocumentForCurrentNode()
     return currentNode().document();
 }
 
-Ref<Element> HTMLConstructionSite::createHTMLElement(AtomicHTMLToken* token)
+RefPtr<Element> HTMLConstructionSite::createHTMLElementOrFindCustomElementInterface(
+    AtomicHTMLToken* token, JSCustomElementInterface** customElementInterface)
 {
-    QualifiedName tagName(nullAtom, token->name(), xhtmlNamespaceURI);
+    auto& localName = token->name();
     // FIXME: This can't use HTMLConstructionSite::createElement because we
     // have to pass the current form element.  We should rework form association
     // to occur after construction to allow better code sharing here.
     // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#create-an-element-for-the-token
     Document& ownerDocument = ownerDocumentForCurrentNode();
     bool insideTemplateElement = !ownerDocument.frame();
-    Ref<Element> element = HTMLElementFactory::createElement(tagName, ownerDocument, insideTemplateElement ? nullptr : form(), true);
-    
+    RefPtr<Element> element = HTMLElementFactory::createKnownElement(localName, ownerDocument, insideTemplateElement ? nullptr : form(), true);
+    if (UNLIKELY(!element)) {
+
+#if ENABLE(CUSTOM_ELEMENTS)
+        auto* definitions = ownerDocumentForCurrentNode().customElementDefinitions();
+        if (customElementInterface && UNLIKELY(definitions)) {
+            if (auto* interface = definitions->findInterface(localName)) {
+                *customElementInterface = interface;
+                return nullptr;
+            }
+        }
+#else
+        UNUSED_PARAM(customElementInterface);
+#endif
+
+        element = HTMLUnknownElement::create(QualifiedName(nullAtom, localName, xhtmlNamespaceURI), ownerDocumentForCurrentNode());
+    }
+    ASSERT(element);
+
     // FIXME: This is a hack to connect images to pictures before the image has
     // been inserted into the document. It can be removed once asynchronous image
     // loading is working.
-    if (is<HTMLPictureElement>(currentNode()) && is<HTMLImageElement>(element))
-        downcast<HTMLImageElement>(element.get()).setPictureElement(&downcast<HTMLPictureElement>(currentNode()));
+    if (is<HTMLPictureElement>(currentNode()) && is<HTMLImageElement>(*element))
+        downcast<HTMLImageElement>(*element).setPictureElement(&downcast<HTMLPictureElement>(currentNode()));
 
-    setAttributes(element.get(), token, m_parserContentPolicy);
+    setAttributes(*element, token, m_parserContentPolicy);
     ASSERT(element->isHTMLElement());
     return element;
 }
 
+Ref<Element> HTMLConstructionSite::createHTMLElement(AtomicHTMLToken* token)
+{
+    RefPtr<Element> element = createHTMLElementOrFindCustomElementInterface(token, nullptr);
+    ASSERT(element);
+    return element.releaseNonNull();
+}
+
 Ref<HTMLStackItem> HTMLConstructionSite::createElementFromSavedToken(HTMLStackItem* item)
 {
     // NOTE: Moving from item -> token -> item copies the Attribute vector twice!
index 8e07d90..26c8299 100644 (file)
@@ -84,6 +84,7 @@ class AtomicHTMLToken;
 class Document;
 class Element;
 class HTMLFormElement;
+class JSCustomElementInterface;
 
 class HTMLConstructionSite {
     WTF_MAKE_NONCOPYABLE(HTMLConstructionSite);
@@ -103,6 +104,10 @@ public:
     void insertCommentOnDocument(AtomicHTMLToken*);
     void insertCommentOnHTMLHtmlElement(AtomicHTMLToken*);
     void insertHTMLElement(AtomicHTMLToken*);
+#if ENABLE(CUSTOM_ELEMENTS)
+    JSCustomElementInterface* insertHTMLElementOrFindCustomElementInterface(AtomicHTMLToken*);
+    void insertCustomElement(Ref<Element>&&, const AtomicString& localName, Vector<Attribute>&);
+#endif
     void insertSelfClosingHTMLElement(AtomicHTMLToken*);
     void insertFormattingElement(AtomicHTMLToken*);
     void insertHTMLHeadElement(AtomicHTMLToken*);
@@ -194,6 +199,7 @@ private:
 
     void findFosterSite(HTMLConstructionSiteTask&);
 
+    RefPtr<Element> createHTMLElementOrFindCustomElementInterface(AtomicHTMLToken*, JSCustomElementInterface**);
     Ref<Element> createHTMLElement(AtomicHTMLToken*);
     Ref<Element> createElement(AtomicHTMLToken*, const AtomicString& namespaceURI);
 
index fe733bd..d1f1804 100644 (file)
@@ -34,6 +34,8 @@
 #include "HTMLPreloadScanner.h"
 #include "HTMLScriptRunner.h"
 #include "HTMLTreeBuilder.h"
+#include "HTMLUnknownElement.h"
+#include "JSCustomElementInterface.h"
 
 namespace WebCore {
 
@@ -188,8 +190,24 @@ void HTMLDocumentParser::runScriptsForPausedTreeBuilder()
 {
     ASSERT(scriptingContentIsAllowed(parserContentPolicy()));
 
+#if ENABLE(CUSTOM_ELEMENTS)
+    if (std::unique_ptr<CustomElementConstructionData> constructionData = m_treeBuilder->takeCustomElementConstructionData()) {
+        ASSERT(!m_treeBuilder->hasParserBlockingScriptWork());
+
+        RefPtr<Element> newElement = constructionData->interface->constructElement(constructionData->name, JSCustomElementInterface::ShouldClearException::Clear);
+        if (!newElement) {
+            // FIXME: This call to docuemnt() is wrong for elements inside a template element.
+            newElement = HTMLUnknownElement::create(QualifiedName(nullAtom, constructionData->name, xhtmlNamespaceURI), *document());
+        }
+
+        m_treeBuilder->didCreateCustomOrCallbackElement(newElement.releaseNonNull(), *constructionData);
+        return;
+    }
+#endif
+
     TextPosition scriptStartPosition = TextPosition::belowRangePosition();
     if (auto scriptElement = m_treeBuilder->takeScriptToProcess(scriptStartPosition)) {
+        ASSERT(!m_treeBuilder->hasParserBlockingScriptWork());
         // We will not have a scriptRunner when parsing a DocumentFragment.
         if (m_scriptRunner)
             m_scriptRunner->execute(scriptElement.release(), scriptStartPosition);
@@ -459,7 +477,7 @@ bool HTMLDocumentParser::isWaitingForScripts() const
     // The script runner will hold the script until its loaded and run. During
     // any of this time, we want to count ourselves as "waiting for a script" and thus
     // run the preload scanner, as well as delay completion of parsing.
-    bool treeBuilderHasBlockingScript = m_treeBuilder->hasParserBlockingScript();
+    bool treeBuilderHasBlockingScript = m_treeBuilder->hasParserBlockingScriptWork();
     bool scriptRunnerHasBlockingScript = m_scriptRunner && m_scriptRunner->hasParserBlockingScript();
     // Since the parser is paused while a script runner has a blocking script, it should
     // never be possible to end up with both objects holding a blocking script.
index c189e22..8ad6dfb 100644 (file)
@@ -40,6 +40,7 @@ class HTMLStackItem : public RefCounted<HTMLStackItem> {
 public:
     // Normal HTMLElementStack and HTMLFormattingElementList items.
     static Ref<HTMLStackItem> create(Ref<Element>&&, AtomicHTMLToken&, const AtomicString& namespaceURI = HTMLNames::xhtmlNamespaceURI);
+    static Ref<HTMLStackItem> create(Ref<Element>&&, const AtomicString&, const Vector<Attribute>&);
 
     // Document fragment or element for parsing context.
     static Ref<HTMLStackItem> create(Element&);
@@ -62,6 +63,7 @@ public:
 
 private:
     HTMLStackItem(Ref<Element>&&, AtomicHTMLToken&, const AtomicString& namespaceURI);
+    HTMLStackItem(Ref<Element>&&, const AtomicString& localName, const AtomicString& namespaceURI, const Vector<Attribute>&);
     explicit HTMLStackItem(Element&);
     explicit HTMLStackItem(DocumentFragment&);
 
@@ -89,6 +91,21 @@ inline Ref<HTMLStackItem> HTMLStackItem::create(Ref<Element>&& element, AtomicHT
     return adoptRef(*new HTMLStackItem(WTFMove(element), token, namespaceURI));
 }
 
+inline HTMLStackItem::HTMLStackItem(Ref<Element>&& element, const AtomicString& localName, const AtomicString& namespaceURI, const Vector<Attribute>& attributes)
+    : m_node(WTFMove(element))
+    , m_namespaceURI(namespaceURI)
+    , m_localName(localName)
+    , m_attributes(attributes)
+{
+    // FIXME: We should find a way to move the attributes vector in the normal code path instead of copying it.
+}
+
+inline Ref<HTMLStackItem> HTMLStackItem::create(Ref<Element>&& element, const AtomicString& localName, const Vector<Attribute>& attributes)
+{
+    auto& namespaceURI = element.get().namespaceURI();
+    return adoptRef(*new HTMLStackItem(WTFMove(element), localName, namespaceURI, attributes));
+}
+
 inline HTMLStackItem::HTMLStackItem(Element& element)
     : m_node(element)
     , m_namespaceURI(element.namespaceURI())
index dda0de8..4b01f36 100644 (file)
@@ -34,6 +34,7 @@
 #include "HTMLFormElement.h"
 #include "HTMLOptGroupElement.h"
 #include "HTMLParserIdioms.h"
+#include "JSCustomElementInterface.h"
 #include "LocalizedStrings.h"
 #include "NotImplemented.h"
 #include "XLinkNames.h"
@@ -50,6 +51,19 @@ namespace WebCore {
 
 using namespace HTMLNames;
 
+#if ENABLE(CUSTOM_ELEMENTS)
+
+CustomElementConstructionData::CustomElementConstructionData(Ref<JSCustomElementInterface>&& interface, const AtomicString& name, const Vector<Attribute>& attributes)
+    : interface(WTFMove(interface))
+    , name(name)
+    , attributes(attributes) // FIXME: Avoid copying attributes.
+{ }
+
+CustomElementConstructionData::~CustomElementConstructionData()
+{ }
+
+#endif
+
 namespace {
 
 inline bool isHTMLSpaceOrReplacementCharacter(UChar character)
@@ -896,9 +910,27 @@ void HTMLTreeBuilder::processStartTagForInBody(AtomicHTMLToken& token)
     }
 #endif
     m_tree.reconstructTheActiveFormattingElements();
+    insertGenericHTMLElement(token);
+}
+
+inline void HTMLTreeBuilder::insertGenericHTMLElement(AtomicHTMLToken& token)
+{
+#if ENABLE(CUSTOM_ELEMENTS)
+    auto* interface = m_tree.insertHTMLElementOrFindCustomElementInterface(&token);
+    if (UNLIKELY(interface))
+        m_customElementToConstruct = std::make_unique<CustomElementConstructionData>(*interface, token.name(), token.attributes());
+#else
     m_tree.insertHTMLElement(&token);
+#endif
 }
 
+#if ENABLE(CUSTOM_ELEMENTS)
+void HTMLTreeBuilder::didCreateCustomOrCallbackElement(Ref<Element>&& element, CustomElementConstructionData& data)
+{
+    m_tree.insertCustomElement(WTFMove(element), data.name, data.attributes);
+}
+#endif
+
 #if ENABLE(TEMPLATE_ELEMENT)
 
 void HTMLTreeBuilder::processTemplateStartTag(AtomicHTMLToken& token)
index 5994ece..4760e8d 100644 (file)
 
 namespace WebCore {
 
+class JSCustomElementInterface;
 class HTMLDocumentParser;
 
+#if ENABLE(CUSTOM_ELEMENTS)
+struct CustomElementConstructionData {
+    CustomElementConstructionData(Ref<JSCustomElementInterface>&&, const AtomicString& name, const Vector<Attribute>&);
+    ~CustomElementConstructionData();
+
+    Ref<JSCustomElementInterface> interface;
+    AtomicString name;
+    Vector<Attribute> attributes;
+};
+#endif
+
 class HTMLTreeBuilder {
     WTF_MAKE_FAST_ALLOCATED;
 public:
@@ -49,11 +61,16 @@ public:
 
     void constructTree(AtomicHTMLToken&);
 
-    bool hasParserBlockingScript() const;
+    bool hasParserBlockingScriptWork() const;
 
     // Must be called to take the parser-blocking script before calling the parser again.
     RefPtr<Element> takeScriptToProcess(TextPosition& scriptStartPosition);
 
+#if ENABLE(CUSTOM_ELEMENTS)
+    std::unique_ptr<CustomElementConstructionData> takeCustomElementConstructionData() { return WTFMove(m_customElementToConstruct); }
+    void didCreateCustomOrCallbackElement(Ref<Element>&&, CustomElementConstructionData&);
+#endif
+
     // Done, close any open tags, etc.
     void finished();
 
@@ -165,6 +182,8 @@ private:
 
     void resetInsertionModeAppropriately();
 
+    void insertGenericHTMLElement(AtomicHTMLToken&);
+
 #if ENABLE(TEMPLATE_ELEMENT)
     void processTemplateStartTag(AtomicHTMLToken&);
     bool processTemplateEndTag(AtomicHTMLToken&);
@@ -204,6 +223,10 @@ private:
     RefPtr<Element> m_scriptToProcess; // <script> tag which needs processing before resuming the parser.
     TextPosition m_scriptToProcessStartPosition; // Starting line number of the script tag needing processing.
 
+#if ENABLE(CUSTOM_ELEMENTS)
+    std::unique_ptr<CustomElementConstructionData> m_customElementToConstruct;
+#endif
+
     bool m_shouldSkipLeadingNewline { false };
 
     bool m_framesetOk { true };
@@ -235,10 +258,15 @@ inline bool HTMLTreeBuilder::isParsingFragment() const
     return !!m_fragmentContext.fragment();
 }
 
-inline bool HTMLTreeBuilder::hasParserBlockingScript() const
+inline bool HTMLTreeBuilder::hasParserBlockingScriptWork() const
 {
     ASSERT(!m_destroyed);
-    return !!m_scriptToProcess;
+#if ENABLE(CUSTOM_ELEMENTS)
+    ASSERT(!(m_scriptToProcess && m_customElementToConstruct));
+    return m_scriptToProcess || m_customElementToConstruct;
+#else
+    return m_scriptToProcess;
+#endif
 }
 
 inline DocumentFragment* HTMLTreeBuilder::FragmentParsingContext::fragment() const