Diff
Modified: trunk/LayoutTests/ChangeLog (205385 => 205386)
--- trunk/LayoutTests/ChangeLog 2016-09-03 00:04:27 UTC (rev 205385)
+++ trunk/LayoutTests/ChangeLog 2016-09-03 00:09:01 UTC (rev 205386)
@@ -1,3 +1,19 @@
+2016-09-02 Ryosuke Niwa <[email protected]>
+
+ Add validations for a synchronously constructed custom element
+ https://bugs.webkit.org/show_bug.cgi?id=161528
+
+ Reviewed by Yusuke Suzuki.
+
+ Added test cases for sanity checks in step 6.1. of https://dom.spec.whatwg.org/#concept-create-element
+ and updated other test cases per those changes.
+
+ * fast/custom-elements/Document-createElement-expected.txt:
+ * fast/custom-elements/Document-createElement.html:
+ * fast/custom-elements/defined-pseudo-class-expected.txt: Rebaselined now that exceptions thrown while constructing
+ a custom element is reported in the console.
+ * fast/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt: Ditto.
+
2016-09-02 Ryan Haddad <[email protected]>
Marking two editing/mac/spelling tests as flaky on mac-wk2.
Modified: trunk/LayoutTests/fast/custom-elements/Document-createElement-expected.txt (205385 => 205386)
--- trunk/LayoutTests/fast/custom-elements/Document-createElement-expected.txt 2016-09-03 00:04:27 UTC (rev 205385)
+++ trunk/LayoutTests/fast/custom-elements/Document-createElement-expected.txt 2016-09-03 00:09:01 UTC (rev 205386)
@@ -1,7 +1,37 @@
PASS document.createElement must create an instance of custom elements
-PASS document.createElement must return null when a custom element constructor returns an object that is not an instance of Node
-PASS document.createElement must return null when a custom element constructor returns a Text node
-PASS document.createElement must return an element returned by a custom element constructor
+PASS document.createElement must throw a TypeError when the result of Construct is not a DOM node
+PASS document.createElement must throw a TypeError when the result of Construct is a TextNode
+PASS document.createElement must throw a NotSupportedError when attribute is added by setAttribute during construction
+PASS document.createElement must throw a NotSupportedError when attribute is added by attributes.setNamedItem during construction
+PASS document.createElement must not throw a NotSupportedError when attribute is added and removed during construction
+PASS document.createElement must throw a NotSupportedError when a Text child is added during construction
+PASS document.createElement must throw a NotSupportedError when a Comment child is added during construction
+PASS document.createElement must throw a NotSupportedError when an element child is added during construction
+PASS document.createElement must not throw a NotSupportedError when an element child is added and removed during construction
+PASS document.createElement must throw a NotSupportedError when the element gets inserted into another element during construction
+PASS document.createElement must not throw a NotSupportedError when the element is inserted and removed from another element during construction
+PASS document.createElement must throw a NotSupportedError when the element is adopted into a document of a template element during construction
+PASS document.createElement must throw a NotSupportedError when the element is inserted into a document of a template element during construction
+PASS document.createElement must not throw a NotSupportedError when the element is adopted back from a document of a template element during construction
+PASS document.createElement must throw a NotSupportedError when the element is adopted into a new document during construction
+PASS document.createElement must throw a NotSupportedError when the element is inserted into a new document during construction
+PASS document.createElement must not throw a NotSupportedError when the element is adopted back from a new document during construction
+PASS document.createElement must throw a NotSupportedError when the element is adopted into a cloned document during construction
+PASS document.createElement must throw a NotSupportedError when the element is inserted into a cloned document during construction
+PASS document.createElement must not throw a NotSupportedError when the element is adopted back from a cloned document during construction
+PASS document.createElement must throw a NotSupportedError when the element is adopted into a document created by createHTMLDocument during construction
+PASS document.createElement must throw a NotSupportedError when the element is inserted into a document created by createHTMLDocument during construction
+PASS document.createElement must not throw a NotSupportedError when the element is adopted back from a document created by createHTMLDocument during construction
+PASS document.createElement must throw a NotSupportedError when the element is adopted into a HTML document created by createDocument during construction
+PASS document.createElement must throw a NotSupportedError when the element is inserted into a HTML document created by createDocument during construction
+PASS document.createElement must not throw a NotSupportedError when the element is adopted back from a HTML document created by createDocument during construction
+PASS document.createElement must throw a NotSupportedError when the element is adopted into a document in an iframe during construction
+PASS document.createElement must throw a NotSupportedError when the element is inserted into a document in an iframe during construction
+PASS document.createElement must not throw a NotSupportedError when the element is adopted back from a document in an iframe during construction
+PASS document.createElement must throw a NotSupportedError when the element is adopted into a HTML document fetched by XHR during construction
+PASS document.createElement must throw a NotSupportedError when the element is inserted into a HTML document fetched by XHR during construction
+PASS document.createElement must not throw a NotSupportedError when the element is adopted back from a HTML document fetched by XHR during construction
+PASS document.createElement must throw a NotSupportedError when the local name of the element does not match that of the custom element
PASS document.createElement must re-throw an exception thrown by a custom element constructor
Modified: trunk/LayoutTests/fast/custom-elements/Document-createElement.html (205385 => 205386)
--- trunk/LayoutTests/fast/custom-elements/Document-createElement.html 2016-09-03 00:04:27 UTC (rev 205385)
+++ trunk/LayoutTests/fast/custom-elements/Document-createElement.html 2016-09-03 00:09:01 UTC (rev 205386)
@@ -7,6 +7,7 @@
<script src=""
<script src=""
<link rel='stylesheet' href=''>
+<script src=""
</head>
<body>
<div id="log"></div>
@@ -39,8 +40,8 @@
assert_true(instance instanceof Object);
assert_equals(instance.foo, 'bar');
- assert_equals(document.createElement('object-custom-element'), null);
-}, 'document.createElement must return null when a custom element constructor returns an object that is not an instance of Node');
+ assert_throws({name: 'TypeError'}, function () { document.createElement('object-custom-element'); });
+}, 'document.createElement must throw a TypeError when the result of Construct is not a DOM node');
test(function () {
class TextCustomElement extends HTMLElement {
@@ -51,27 +52,196 @@
};
customElements.define('text-custom-element', TextCustomElement);
assert_true(new TextCustomElement instanceof Text);
- assert_equals(document.createElement('object-custom-element'), null);
-}, 'document.createElement must return null when a custom element constructor returns a Text node');
+ assert_throws({name: 'TypeError'}, function () { document.createElement('text-custom-element'); });
+}, 'document.createElement must throw a TypeError when the result of Construct is a TextNode');
test(function () {
- var createdElement = null;
+ class ElementWithAttribute extends HTMLElement {
+ constructor()
+ {
+ super();
+ this.setAttribute('id', 'foo');
+ }
+ };
+ customElements.define('element-with-attribute', ElementWithAttribute);
+ assert_true(new ElementWithAttribute instanceof ElementWithAttribute);
+ assert_throws({name: 'NotSupportedError'}, function () { document.createElement('element-with-attribute'); });
+}, 'document.createElement must throw a NotSupportedError when attribute is added by setAttribute during construction');
+
+test(function () {
+ class ElementWithAttrNode extends HTMLElement {
+ constructor()
+ {
+ super();
+ this.attributes.setNamedItem(document.createAttribute('title'));
+ }
+ };
+ customElements.define('element-with-attr-node', ElementWithAttrNode);
+ assert_true(new ElementWithAttrNode instanceof ElementWithAttrNode);
+ assert_throws({name: 'NotSupportedError'}, function () { document.createElement('element-with-attr-node'); });
+}, 'document.createElement must throw a NotSupportedError when attribute is added by attributes.setNamedItem during construction');
+
+test(function () {
+ class ElementWithNoAttributes extends HTMLElement {
+ constructor()
+ {
+ super();
+ this.attributes.setNamedItem(document.createAttribute('title'));
+ this.removeAttribute('title');
+ }
+ };
+ customElements.define('element-with-no-attiributes', ElementWithNoAttributes);
+ assert_true(new ElementWithNoAttributes instanceof ElementWithNoAttributes);
+ assert_true(document.createElement('element-with-no-attiributes') instanceof ElementWithNoAttributes);
+}, 'document.createElement must not throw a NotSupportedError when attribute is added and removed during construction');
+
+test(function () {
+ class ElementWithChildText extends HTMLElement {
+ constructor()
+ {
+ super();
+ this.appendChild(document.createTextNode('hello'));
+ }
+ };
+ customElements.define('element-with-child-text', ElementWithChildText);
+ assert_true(new ElementWithChildText instanceof ElementWithChildText);
+ assert_throws({name: 'NotSupportedError'}, function () { document.createElement('element-with-child-text'); });
+}, 'document.createElement must throw a NotSupportedError when a Text child is added during construction');
+
+test(function () {
+ class ElementWithChildComment extends HTMLElement {
+ constructor()
+ {
+ super();
+ this.appendChild(document.createComment('hello'));
+ }
+ };
+ customElements.define('element-with-child-comment', ElementWithChildComment);
+ assert_true(new ElementWithChildComment instanceof ElementWithChildComment);
+ assert_throws({name: 'NotSupportedError'}, function () { document.createElement('element-with-child-comment'); });
+}, 'document.createElement must throw a NotSupportedError when a Comment child is added during construction');
+
+test(function () {
+ class ElementWithChildElement extends HTMLElement {
+ constructor()
+ {
+ super();
+ this.appendChild(document.createElement('div'));
+ }
+ };
+ customElements.define('element-with-child-element', ElementWithChildElement);
+ assert_true(new ElementWithChildElement instanceof ElementWithChildElement);
+ assert_throws({name: 'NotSupportedError'}, function () { document.createElement('element-with-child-element'); });
+}, 'document.createElement must throw a NotSupportedError when an element child is added during construction');
+
+test(function () {
+ class ElementWithNoChildElements extends HTMLElement {
+ constructor()
+ {
+ super();
+ this.appendChild(document.createElement('div'));
+ this.removeChild(this.firstChild);
+ }
+ };
+ customElements.define('element-with-no-child-elements', ElementWithNoChildElements);
+ assert_true(document.createElement('element-with-no-child-elements') instanceof ElementWithNoChildElements);
+}, 'document.createElement must not throw a NotSupportedError when an element child is added and removed during construction');
+
+test(function () {
+ class ElementWithParent extends HTMLElement {
+ constructor()
+ {
+ super();
+ document.createElement('div').appendChild(this);
+ }
+ };
+ customElements.define('element-with-parent', ElementWithParent);
+ assert_true(new ElementWithParent instanceof ElementWithParent);
+ assert_throws({name: 'NotSupportedError'}, function () { document.createElement('element-with-parent'); });
+}, 'document.createElement must throw a NotSupportedError when the element gets inserted into another element during construction');
+
+test(function () {
+ class ElementWithNoParent extends HTMLElement {
+ constructor()
+ {
+ super();
+ document.createElement('div').appendChild(this);
+ this.parentNode.removeChild(this);
+ }
+ };
+ customElements.define('element-with-no-parent', ElementWithNoParent);
+ assert_true(document.createElement('element-with-no-parent') instanceof ElementWithNoParent);
+}, 'document.createElement must not throw a NotSupportedError when the element is inserted and removed from another element during construction');
+
+DocumentTypes.forEach(function (entry, testNumber) {
+ if (entry.isOwner)
+ return;
+
+ var getDocument = entry.create;
+ var docuemntName = entry.name;
+
+ promise_test(function () {
+ return getDocument().then(function (doc) {
+ class ElementWithAdoptCall extends HTMLElement {
+ constructor()
+ {
+ super();
+ doc.adoptNode(this);
+ }
+ };
+ var name = 'element-with-adopt-call-' + testNumber;
+ customElements.define(name, ElementWithAdoptCall);
+ assert_true(new ElementWithAdoptCall instanceof ElementWithAdoptCall);
+ assert_throws({name: 'NotSupportedError'}, function () { document.createElement(name); });
+ });
+ }, 'document.createElement must throw a NotSupportedError when the element is adopted into a ' + docuemntName + ' during construction');
+
+ promise_test(function () {
+ return getDocument().then(function (doc) {
+ class ElementInsertedIntoAnotherDocument extends HTMLElement {
+ constructor()
+ {
+ super();
+ doc.documentElement.appendChild(this);
+ }
+ };
+ var name = 'element-inserted-into-another-document-' + testNumber;
+ customElements.define(name, ElementInsertedIntoAnotherDocument);
+ assert_true(new ElementInsertedIntoAnotherDocument instanceof ElementInsertedIntoAnotherDocument);
+ assert_throws({name: 'NotSupportedError'}, function () { document.createElement(name); });
+ });
+ }, 'document.createElement must throw a NotSupportedError when the element is inserted into a ' + docuemntName + ' during construction');
+
+ promise_test(function () {
+ return getDocument().then(function (doc) {
+ class ElementThatGetAdoptedBack extends HTMLElement {
+ constructor()
+ {
+ super();
+ doc.adoptNode(this);
+ document.adoptNode(this);
+ }
+ };
+ var name = 'element-that-get-adopted-back' + testNumber;
+ customElements.define(name, ElementThatGetAdoptedBack);
+ assert_true(document.createElement(name) instanceof ElementThatGetAdoptedBack);
+ });
+ }, 'document.createElement must not throw a NotSupportedError when the element is adopted back from a ' + docuemntName + ' during construction');
+});
+
+test(function () {
class DivCustomElement extends HTMLElement {
constructor()
{
super();
- createdElement = document.createElement('div');
- return createdElement;
+ return document.createElement('div');
}
};
customElements.define('div-custom-element', DivCustomElement);
assert_true(new DivCustomElement instanceof HTMLDivElement);
+ assert_throws({name: 'NotSupportedError'}, function () { document.createElement('div-custom-element'); });
+}, 'document.createElement must throw a NotSupportedError when the local name of the element does not match that of the custom element');
- var instance = document.createElement('div-custom-element');
- assert_true(instance instanceof HTMLDivElement);
- assert_equals(instance, createdElement);
-}, 'document.createElement must return an element returned by a custom element constructor');
-
test(function () {
var exceptionToThrow = {message: 'exception thrown by a custom constructor'};
class ThrowCustomElement extends HTMLElement {
Modified: trunk/LayoutTests/fast/custom-elements/defined-pseudo-class-expected.txt (205385 => 205386)
--- trunk/LayoutTests/fast/custom-elements/defined-pseudo-class-expected.txt 2016-09-03 00:04:27 UTC (rev 205385)
+++ trunk/LayoutTests/fast/custom-elements/defined-pseudo-class-expected.txt 2016-09-03 00:09:01 UTC (rev 205386)
@@ -1,4 +1,7 @@
+CONSOLE MESSAGE: line 75: TypeError: The result of constructing a custom element must be a HTMLElement
+Harness Error (FAIL), message = TypeError: The result of constructing a custom element must be a HTMLElement
+
PASS The defined flag of a custom element must not be set if a custom element has not been upgraded yet
PASS The defined flag of a custom element must not be set if a custom element has not been upgraded yet even if the element has been defined
PASS The defined flag of a custom element must be set when a custom element is successfully upgraded
Modified: trunk/LayoutTests/fast/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt (205385 => 205386)
--- trunk/LayoutTests/fast/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt 2016-09-03 00:04:27 UTC (rev 205385)
+++ trunk/LayoutTests/fast/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt 2016-09-03 00:09:01 UTC (rev 205386)
@@ -1,4 +1,10 @@
+CONSOLE MESSAGE: TypeError: The result of constructing a custom element must be a HTMLElement
+CONSOLE MESSAGE: TypeError: The result of constructing a custom element must be a HTMLElement
+CONSOLE MESSAGE: line 32: ReferenceError: Cannot access uninitialized variable.
+CONSOLE MESSAGE: line 38: Bad
+Harness Error (FAIL), message = Bad
+
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()
Modified: trunk/Source/WebCore/ChangeLog (205385 => 205386)
--- trunk/Source/WebCore/ChangeLog 2016-09-03 00:04:27 UTC (rev 205385)
+++ trunk/Source/WebCore/ChangeLog 2016-09-03 00:09:01 UTC (rev 205386)
@@ -1,3 +1,30 @@
+2016-09-02 Ryosuke Niwa <[email protected]>
+
+ Add validations for a synchronously constructed custom element
+ https://bugs.webkit.org/show_bug.cgi?id=161528
+
+ Reviewed by Yusuke Suzuki.
+
+ The latest DOM specification has sanity checks when creating an element with the synchronous custom elements flag set
+ in 6.1.3 through 10:
+ 3. If result does not implement the HTMLElement interface, throw a TypeError.
+ 4. If result's attribute list is not empty, then throw a NotSupportedError.
+ 5. If result has children, then throw a NotSupportedError.
+ 6. If result's parent is not null, then throw a NotSupportedError.
+ 7. If result's node document is not document, then throw a NotSupportedError.
+ 8. If result's namespace is not the HTML namespace, then throw a NotSupportedError.
+ 9. If result's local name is not equal to localName, then throw a NotSupportedError.
+
+ Add all these checks to JSCustomElementInterface::constructElement.
+
+ Tests: fast/custom-elements/Document-createElement.html
+
+ * bindings/js/JSCustomElementInterface.cpp:
+ (WebCore::JSCustomElementInterface::constructElement): Report the exception thrown during parsing instead of just
+ clearing and ignoring it.
+ (WebCore::constructCustomElementSynchronously): Extracted out of constructElement so that we can also catch TypeError
+ and NotSupportedError we throw in constructElement for the parser.
+
2016-09-02 Zalan Bujtas <[email protected]>
ASSERT_NOT_REACHED() is touched in WebCore::valueForLength
Modified: trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp (205385 => 205386)
--- trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp 2016-09-03 00:04:27 UTC (rev 205385)
+++ trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp 2016-09-03 00:09:01 UTC (rev 205386)
@@ -31,6 +31,7 @@
#if ENABLE(CUSTOM_ELEMENTS)
#include "DOMWrapperWorld.h"
+#include "JSDOMBinding.h"
#include "JSDOMGlobalObject.h"
#include "JSElement.h"
#include "JSHTMLElement.h"
@@ -56,7 +57,9 @@
{
}
-RefPtr<Element> JSCustomElementInterface::constructElement(const AtomicString& tagName, ShouldClearException shouldClearException)
+static RefPtr<Element> constructCustomElementSynchronously(Document&, VM&, ExecState&, JSObject* constructor, const AtomicString& localName);
+
+RefPtr<Element> JSCustomElementInterface::constructElement(const AtomicString& localName, ShouldClearException shouldClearException)
{
if (!canInvokeCallback())
return nullptr;
@@ -63,7 +66,8 @@
Ref<JSCustomElementInterface> protectedThis(*this);
- JSLockHolder lock(m_isolatedWorld->vm());
+ VM& vm = m_isolatedWorld->vm();
+ JSLockHolder lock(vm);
if (!m_constructor)
return nullptr;
@@ -72,11 +76,30 @@
if (!context)
return nullptr;
ASSERT(context->isDocument());
- JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(context, *m_isolatedWorld);
- ExecState* state = globalObject->globalExec();
+ auto& state = *context->execState();
+ RefPtr<Element> element = constructCustomElementSynchronously(downcast<Document>(*context), vm, state, m_constructor.get(), localName);
+ if (!element) {
+ auto* exception = vm.exception();
+ ASSERT(exception);
+ if (shouldClearException == ShouldClearException::Clear) {
+ state.clearException();
+ reportException(&state, exception);
+ }
+ return nullptr;
+ }
+
+ element->setCustomElementIsResolved(*this);
+ return element;
+}
+
+// https://dom.spec.whatwg.org/#concept-create-element
+// 6. 1. If the synchronous custom elements flag is set
+static RefPtr<Element> constructCustomElementSynchronously(Document& document, VM& vm, ExecState& state, JSObject* constructor, const AtomicString& localName)
+{
+ auto scope = DECLARE_THROW_SCOPE(vm);
ConstructData constructData;
- ConstructType constructType = m_constructor->methodTable()->getConstructData(m_constructor.get(), constructData);
+ ConstructType constructType = constructor->methodTable()->getConstructData(constructor, constructData);
if (constructType == ConstructType::None) {
ASSERT_NOT_REACHED();
return nullptr;
@@ -83,22 +106,43 @@
}
MarkedArgumentBuffer args;
- args.append(jsStringWithCache(state, tagName));
+ args.append(jsStringWithCache(&state, localName));
- InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionConstruct(context, constructType, constructData);
- JSValue newElement = construct(state, m_constructor.get(), constructType, constructData, args);
- InspectorInstrumentation::didCallFunction(cookie, context);
+ InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionConstruct(&document, constructType, constructData);
+ JSValue newElement = construct(&state, constructor, constructType, constructData, args);
+ InspectorInstrumentation::didCallFunction(cookie, &document);
+ if (vm.exception())
+ return nullptr;
- if (shouldClearException == ShouldClearException::Clear && state->hadException())
- state->clearException();
+ ASSERT(!newElement.isEmpty());
+ HTMLElement* wrappedElement = JSHTMLElement::toWrapped(newElement);
+ if (!wrappedElement) {
+ throwTypeError(&state, scope, ASCIILiteral("The result of constructing a custom element must be a HTMLElement"));
+ return nullptr;
+ }
- if (newElement.isEmpty())
+ if (wrappedElement->hasAttributes()) {
+ throwNotSupportedError(state, scope, ASCIILiteral("A newly constructed custom element must not have attributes"));
return nullptr;
+ }
+ if (wrappedElement->hasChildNodes()) {
+ throwNotSupportedError(state, scope, ASCIILiteral("A newly constructed custom element must not have child nodes"));
+ return nullptr;
+ }
+ if (wrappedElement->parentNode()) {
+ throwNotSupportedError(state, scope, ASCIILiteral("A newly constructed custom element must not have a parent node"));
+ return nullptr;
+ }
+ if (&wrappedElement->document() != &document) {
+ throwNotSupportedError(state, scope, ASCIILiteral("A newly constructed custom element belongs to a wrong docuemnt"));
+ return nullptr;
+ }
+ ASSERT(wrappedElement->namespaceURI() == HTMLNames::xhtmlNamespaceURI);
+ if (wrappedElement->localName() != localName) {
+ throwNotSupportedError(state, scope, ASCIILiteral("A newly constructed custom element belongs to a wrong docuemnt"));
+ return nullptr;
+ }
- Element* wrappedElement = JSElement::toWrapped(newElement);
- if (!wrappedElement)
- return nullptr;
- wrappedElement->setCustomElementIsResolved(*this);
return wrappedElement;
}