Source/WebCore:
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 4 Mar 2016 00:28:44 +0000 (00:28 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 4 Mar 2016 00:28:44 +0000 (00:28 +0000)
Disallow custom elements inside a window-less documents
https://bugs.webkit.org/show_bug.cgi?id=154944
<rdar://problem/24944875>

Reviewed by Antti Koivisto.

Disallow custom elements inside a window-less documents such as the shared inert document of template elements
and the ones created by DOMImplementation.createDocument and DOMImplementation.createHTMLDocument.

Throw NotSupportedError in defineCustomElement when it's called in such a document as discussed in:
https://github.com/w3c/webcomponents/issues/369

Tests: fast/custom-elements/parser/parser-constructs-custom-element-in-document-write.html
       fast/custom-elements/parser/parser-uses-registry-of-owner-document.html

* bindings/js/JSDOMBinding.cpp:
(WebCore::throwNotSupportedError): Added.
* bindings/js/JSDOMBinding.h:
* bindings/js/JSDocumentCustom.cpp:
(WebCore::JSDocument::defineCustomElement): Throw NotSupportedError when the context object's document doesn't
have a browsing context (i.e. window-less).
* html/parser/HTMLDocumentParser.cpp:
(WebCore::HTMLDocumentParser::runScriptsForPausedTreeBuilder): Replaced a FIXME with an assertion now that we
disallow instantiation of custom elements inside a template element.

LayoutTests:
Disallow custom elements inside template elements and share the registry for windowless documents
https://bugs.webkit.org/show_bug.cgi?id=154944
<rdar://problem/24944875>

Reviewed by Antti Koivisto.

Added various tests to ensure the custom elements registry is not shared between documents with
distinct browsing context (e.g. iframes) but shared among the ones that share a single browsing context
(e.g. documents created by DOMImplementation).

Also added a test case for defineCustomElement to ensure it throws NotSupportedError when it's called on
a template element's inert owner document as well as a basic test case for document.write.

* fast/custom-elements/Document-defineCustomElement-expected.txt:
* fast/custom-elements/Document-defineCustomElement.html: Added a new test case.
* fast/custom-elements/parser/parser-constructs-custom-element-in-document-write-expected.txt: Added.
* fast/custom-elements/parser/parser-constructs-custom-element-in-document-write.html: Added.
* fast/custom-elements/parser/parser-uses-registry-of-owner-document-expected.txt: Added.
* fast/custom-elements/parser/parser-uses-registry-of-owner-document.html: Added.

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

13 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/custom-elements/Document-defineCustomElement-expected.txt
LayoutTests/fast/custom-elements/Document-defineCustomElement.html
LayoutTests/fast/custom-elements/parser/parser-constructs-custom-element-in-document-write-expected.txt [new file with mode: 0644]
LayoutTests/fast/custom-elements/parser/parser-constructs-custom-element-in-document-write.html [new file with mode: 0644]
LayoutTests/fast/custom-elements/parser/parser-uses-registry-of-owner-document-expected.txt [new file with mode: 0644]
LayoutTests/fast/custom-elements/parser/parser-uses-registry-of-owner-document.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/JSDOMBinding.cpp
Source/WebCore/bindings/js/JSDOMBinding.h
Source/WebCore/bindings/js/JSDocumentCustom.cpp
Source/WebCore/html/parser/HTMLDocumentParser.cpp
Source/WebCore/html/parser/HTMLTreeBuilder.h

index 712266d..69bcdea 100644 (file)
@@ -1,3 +1,25 @@
+2016-03-02  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Disallow custom elements inside template elements and share the registry for windowless documents
+        https://bugs.webkit.org/show_bug.cgi?id=154944
+        <rdar://problem/24944875>
+
+        Reviewed by Antti Koivisto.
+
+        Added various tests to ensure the custom elements registry is not shared between documents with
+        distinct browsing context (e.g. iframes) but shared among the ones that share a single browsing context
+        (e.g. documents created by DOMImplementation).
+
+        Also added a test case for defineCustomElement to ensure it throws NotSupportedError when it's called on
+        a template element's inert owner document as well as a basic test case for document.write.
+
+        * fast/custom-elements/Document-defineCustomElement-expected.txt:
+        * fast/custom-elements/Document-defineCustomElement.html: Added a new test case.
+        * fast/custom-elements/parser/parser-constructs-custom-element-in-document-write-expected.txt: Added.
+        * fast/custom-elements/parser/parser-constructs-custom-element-in-document-write.html: Added.
+        * fast/custom-elements/parser/parser-uses-registry-of-owner-document-expected.txt: Added.
+        * fast/custom-elements/parser/parser-uses-registry-of-owner-document.html: Added.
+
 2016-03-03  Zalan Bujtas  <zalan@apple.com>
 
         Subpixel rendering: Make collapsed borders painting subpixel aware.
index 56e94be..3eb709f 100644 (file)
@@ -2,6 +2,9 @@
 PASS Check the existence of defineCustomElement on Document interface 
 PASS document.defineCustomElement should throw with an invalid name 
 PASS document.defineCustomElement should throw with a duplicate name 
+PASS document.defineCustomElement must throw a NotSupportedError when the context object is an associated inert template document 
+PASS document.defineCustomElement must throw a NotSupportedError when the context object is created by DOMImplementation.createHTMLDocument 
+PASS document.defineCustomElement must throw a NotSupportedError when the context object is created by DOMImplementation.createDocument 
 PASS document.defineCustomElement should throw when the element interface is not a constructor 
 PASS document.defineCustomElement should define an instantiatable custom element 
 
index e53f1b2..e37e76e 100644 (file)
@@ -58,6 +58,36 @@ test(function () {
 }, 'document.defineCustomElement should throw with a duplicate name');
 
 test(function () {
+    class SomeCustomElement extends HTMLElement {};
+
+    var templateContentOwnerDocument = document.createElement('template').content.ownerDocument;
+    assert_throws({'name': 'NotSupportedError'}, function () {
+        templateContentOwnerDocument.defineCustomElement('some-custom-element', SomeCustomElement);
+    });
+
+}, 'document.defineCustomElement must throw a NotSupportedError when the context object is an associated inert template document');
+
+test(function () {
+    class SomeCustomElement extends HTMLElement {};
+
+    var windowlessDocument = document.implementation.createHTMLDocument();
+    assert_throws({'name': 'NotSupportedError'}, function () {
+        windowlessDocument.defineCustomElement('some-custom-element', SomeCustomElement);
+    });
+
+}, 'document.defineCustomElement must throw a NotSupportedError when the context object is created by DOMImplementation.createHTMLDocument');
+
+test(function () {
+    class SomeCustomElement extends HTMLElement {};
+
+    var windowlessDocument = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null)
+    assert_throws({'name': 'NotSupportedError'}, function () {
+        windowlessDocument.defineCustomElement('some-custom-element', SomeCustomElement);
+    });
+
+}, 'document.defineCustomElement must throw a NotSupportedError when the context object is created by DOMImplementation.createDocument');
+
+test(function () {
     assert_throws({'name': 'TypeError'}, function () { document.defineCustomElement('invalid-element', 1); },
         'document.defineCustomElement must throw a TypeError when the element interface is a number');
     assert_throws({'name': 'TypeError'}, function () { document.defineCustomElement('invalid-element', '123'); },
diff --git a/LayoutTests/fast/custom-elements/parser/parser-constructs-custom-element-in-document-write-expected.txt b/LayoutTests/fast/custom-elements/parser/parser-constructs-custom-element-in-document-write-expected.txt
new file mode 100644 (file)
index 0000000..97af299
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS HTML parser must instantiate custom elements inside document.write 
+
diff --git a/LayoutTests/fast/custom-elements/parser/parser-constructs-custom-element-in-document-write.html b/LayoutTests/fast/custom-elements/parser/parser-constructs-custom-element-in-document-write.html
new file mode 100644 (file)
index 0000000..c914c2d
--- /dev/null
@@ -0,0 +1,30 @@
+<!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 custom elements inside document.write">
+<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 MyCustomElement extends HTMLElement { }
+document.defineCustomElement('my-custom-element', MyCustomElement);
+
+document.write('<my-custom-element></my-custom-element>');
+
+test(function () {
+    var instance = document.querySelector('my-custom-element');
+
+    assert_true(instance instanceof HTMLElement);
+    assert_true(instance instanceof MyCustomElement);
+
+}, 'HTML parser must instantiate custom elements inside document.write');
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/custom-elements/parser/parser-uses-registry-of-owner-document-expected.txt b/LayoutTests/fast/custom-elements/parser/parser-uses-registry-of-owner-document-expected.txt
new file mode 100644 (file)
index 0000000..7221690
--- /dev/null
@@ -0,0 +1,8 @@
+
+PASS HTML parser must not instantiate custom elements inside template elements 
+PASS HTML parser must not use the registry of the owner element's document inside an iframe 
+PASS HTML parser must use the registry of the content document inside an iframe 
+PASS HTML parser must not instantiate a custom element defined inside an frame in frame element's owner document 
+PASS HTML parser must use the registry of window.document in a document created by document.implementation.createHTMLDocument() 
+PASS HTML parser must use the registry of window.document in a document created by document.implementation.createXHTMLDocument() 
+
diff --git a/LayoutTests/fast/custom-elements/parser/parser-uses-registry-of-owner-document.html b/LayoutTests/fast/custom-elements/parser/parser-uses-registry-of-owner-document.html
new file mode 100644 (file)
index 0000000..fed2011
--- /dev/null
@@ -0,0 +1,98 @@
+<!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 use the owner document's custom element registry">
+<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 MyCustomElement extends HTMLElement { };
+document.defineCustomElement('my-custom-element', MyCustomElement);
+
+document.write('<template><my-custom-element></my-custom-element></template>');
+
+test(function () {
+    var template = document.querySelector('template');
+    var instance = template.content.firstChild;
+
+    assert_true(instance instanceof HTMLElement,
+        'A custom element inside a template element must be an instance of HTMLElement');
+    assert_false(instance instanceof MyCustomElement,
+        'A custom element must not be instantiated inside a template element using the registry of the template element\'s owner document');
+    assert_equals(instance.ownerDocument, template.content.ownerDocument,
+        'Custom elements inside a template must use the appropriate template contents owner document as the owner document');
+
+}, 'HTML parser must not instantiate custom elements inside template elements');
+
+var iframe = document.createElement('iframe');
+document.body.appendChild(iframe);
+iframe.contentDocument.body.innerHTML = '<my-custom-element></my-custom-element>';
+
+test(function () {
+    var instance = iframe.contentDocument.querySelector('my-custom-element');
+
+    assert_true(instance instanceof iframe.contentWindow.HTMLElement);
+    assert_false(instance instanceof MyCustomElement);
+
+}, 'HTML parser must not use the registry of the owner element\'s document inside an iframe');
+
+class ElementInIFrame extends iframe.contentWindow.HTMLElement { };
+iframe.contentDocument.defineCustomElement('element-in-iframe', ElementInIFrame);
+iframe.contentDocument.body.innerHTML = '<element-in-iframe></element-in-iframe>';
+
+test(function () {
+    var instance = iframe.contentDocument.querySelector('element-in-iframe');
+
+    assert_true(instance instanceof iframe.contentWindow.HTMLElement, 'A custom element inside an iframe must be an instance of HTMLElement');
+    assert_true(instance instanceof ElementInIFrame,
+        'A custom element must be instantiated inside an iframe using the registry of the content document');
+    assert_equals(instance.ownerDocument, iframe.contentDocument,
+        'The owner document of custom elements inside an iframe must be the content document of the iframe');
+
+}, 'HTML parser must use the registry of the content document inside an iframe');
+
+document.write('<element-in-iframe></element-in-iframe>');
+
+test(function () {
+    var instance = document.querySelector('element-in-iframe');
+
+    assert_true(instance instanceof HTMLElement);
+    assert_false(instance instanceof ElementInIFrame);
+
+}, 'HTML parser must not instantiate a custom element defined inside an frame in frame element\'s owner document');
+
+document.body.removeChild(iframe);
+
+var windowlessDocument = document.implementation.createHTMLDocument();
+windowlessDocument.open();
+windowlessDocument.write('<my-custom-element></my-custom-element>');
+windowlessDocument.close();
+
+test(function () {
+    var instance = windowlessDocument.querySelector('my-custom-element');
+
+    assert_true(instance instanceof HTMLElement);
+    assert_false(instance instanceof MyCustomElement);
+
+}, 'HTML parser must use the registry of window.document in a document created by document.implementation.createHTMLDocument()');
+
+windowlessDocument = document.implementation.createDocument ('http://www.w3.org/1999/xhtml', 'html', null);
+windowlessDocument.documentElement.innerHTML = '<my-custom-element></my-custom-element>';
+
+test(function () {
+    var instance = windowlessDocument.querySelector('my-custom-element');
+
+    assert_true(instance instanceof HTMLElement);
+    assert_false(instance instanceof MyCustomElement);
+
+}, 'HTML parser must use the registry of window.document in a document created by document.implementation.createXHTMLDocument()');
+
+</script>
+</body>
+</html>
index 18bd6ac..494442c 100644 (file)
@@ -1,3 +1,30 @@
+2016-03-03  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Disallow custom elements inside a window-less documents
+        https://bugs.webkit.org/show_bug.cgi?id=154944
+        <rdar://problem/24944875>
+
+        Reviewed by Antti Koivisto.
+
+        Disallow custom elements inside a window-less documents such as the shared inert document of template elements
+        and the ones created by DOMImplementation.createDocument and DOMImplementation.createHTMLDocument.
+
+        Throw NotSupportedError in defineCustomElement when it's called in such a document as discussed in:
+        https://github.com/w3c/webcomponents/issues/369
+
+        Tests: fast/custom-elements/parser/parser-constructs-custom-element-in-document-write.html
+               fast/custom-elements/parser/parser-uses-registry-of-owner-document.html
+
+        * bindings/js/JSDOMBinding.cpp:
+        (WebCore::throwNotSupportedError): Added.
+        * bindings/js/JSDOMBinding.h:
+        * bindings/js/JSDocumentCustom.cpp:
+        (WebCore::JSDocument::defineCustomElement): Throw NotSupportedError when the context object's document doesn't
+        have a browsing context (i.e. window-less).
+        * html/parser/HTMLDocumentParser.cpp:
+        (WebCore::HTMLDocumentParser::runScriptsForPausedTreeBuilder): Replaced a FIXME with an assertion now that we
+        disallow instantiation of custom elements inside a template element.
+
 2016-03-03  Alex Christensen  <achristensen@webkit.org>
 
         Move SPI to CFNetworkSPI.h
index b364df0..83b613c 100644 (file)
@@ -635,6 +635,13 @@ void reportDeprecatedSetterError(JSC::ExecState& state, const char* interfaceNam
     context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("Deprecated attempt to set property '", attributeName, "' on a non-", interfaceName, " object."));
 }
 
+void throwNotSupportedError(JSC::ExecState& state, const char* message)
+{
+    ASSERT(!state.hadException());
+    String messageString(message);
+    state.vm().throwException(&state, createDOMException(&state, NOT_SUPPORTED_ERR, &messageString));
+}
+
 JSC::EncodedJSValue throwArgumentMustBeEnumError(JSC::ExecState& state, unsigned argumentIndex, const char* argumentName, const char* functionInterfaceName, const char* functionName, const char* expectedValues)
 {
     StringBuilder builder;
index 45e4c85..3b2fd37 100644 (file)
@@ -84,6 +84,7 @@ DOMWindow& firstDOMWindow(JSC::ExecState*);
 WEBCORE_EXPORT JSC::EncodedJSValue reportDeprecatedGetterError(JSC::ExecState&, const char* interfaceName, const char* attributeName);
 WEBCORE_EXPORT void reportDeprecatedSetterError(JSC::ExecState&, const char* interfaceName, const char* attributeName);
 
+void throwNotSupportedError(JSC::ExecState&, const char* message);
 void throwArrayElementTypeError(JSC::ExecState&);
 void throwAttributeTypeError(JSC::ExecState&, const char* interfaceName, const char* attributeName, const char* expectedType);
 WEBCORE_EXPORT void throwSequenceTypeError(JSC::ExecState&);
index 75634b2..77034a3 100644 (file)
@@ -147,6 +147,11 @@ JSValue JSDocument::defineCustomElement(ExecState& state)
         return throwTypeError(&state, "The second argument must be a constructor");
 
     Document& document = wrapped();
+    if (!document.domWindow()) {
+        throwNotSupportedError(state, "Cannot define a custom element in a docuemnt without a browsing context");
+        return jsUndefined();
+    }
+
     switch (CustomElementDefinitions::checkName(tagName)) {
     case CustomElementDefinitions::NameStatus::Valid:
         break;
@@ -161,10 +166,7 @@ JSValue JSDocument::defineCustomElement(ExecState& state)
     QualifiedName name(nullAtom, tagName, HTMLNames::xhtmlNamespaceURI);
     auto& definitions = document.ensureCustomElementDefinitions();
     if (definitions.findInterface(tagName)) {
-        ExceptionCodeWithMessage ec;
-        ec.code = NOT_SUPPORTED_ERR;
-        ec.message = "Cannot define multiple custom elements with the same tag name";
-        setDOMException(&state, ec);
+        throwNotSupportedError(state, "Cannot define multiple custom elements with the same tag name");
         return jsUndefined();
     }
     definitions.defineElement(name, JSCustomElementInterface::create(object, globalObject()));
index d1f1804..2e48ad4 100644 (file)
@@ -196,7 +196,7 @@ void HTMLDocumentParser::runScriptsForPausedTreeBuilder()
 
         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.
+            ASSERT(!m_treeBuilder->isParsingTemplateContents());
             newElement = HTMLUnknownElement::create(QualifiedName(nullAtom, constructionData->name, xhtmlNamespaceURI), *document());
         }
 
index 4760e8d..d1be501 100644 (file)
@@ -61,6 +61,7 @@ public:
 
     void constructTree(AtomicHTMLToken&);
 
+    bool isParsingTemplateContents() const;
     bool hasParserBlockingScriptWork() const;
 
     // Must be called to take the parser-blocking script before calling the parser again.
@@ -107,7 +108,6 @@ private:
         AfterAfterFrameset,
     };
 
-    bool isParsingTemplateContents() const;
     bool isParsingFragmentOrTemplateContents() const;
 
 #if ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(IOS)