Skip to content

Commit

Permalink
Source/WebCore:
Browse files Browse the repository at this point in the history
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:
WICG/webcomponents#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.


Canonical link: https://commits.webkit.org/173073@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@197528 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
rniwa committed Mar 4, 2016
1 parent 471a5b9 commit a5e967d
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 6 deletions.
22 changes: 22 additions & 0 deletions LayoutTests/ChangeLog
@@ -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.
Expand Down
Expand Up @@ -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

30 changes: 30 additions & 0 deletions LayoutTests/fast/custom-elements/Document-defineCustomElement.html
Expand Up @@ -57,6 +57,36 @@

}, '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');
Expand Down
@@ -0,0 +1,3 @@

PASS HTML parser must instantiate custom elements inside document.write

@@ -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>
@@ -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()

@@ -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>
27 changes: 27 additions & 0 deletions Source/WebCore/ChangeLog
@@ -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
Expand Down
7 changes: 7 additions & 0 deletions Source/WebCore/bindings/js/JSDOMBinding.cpp
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/bindings/js/JSDOMBinding.h
Expand Up @@ -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&);
Expand Down
10 changes: 6 additions & 4 deletions Source/WebCore/bindings/js/JSDocumentCustom.cpp
Expand Up @@ -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;
Expand All @@ -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()));
Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/html/parser/HTMLDocumentParser.cpp
Expand Up @@ -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());
}

Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/html/parser/HTMLTreeBuilder.h
Expand Up @@ -61,6 +61,7 @@ class HTMLTreeBuilder {

void constructTree(AtomicHTMLToken&);

bool isParsingTemplateContents() const;
bool hasParserBlockingScriptWork() const;

// Must be called to take the parser-blocking script before calling the parser again.
Expand Down Expand Up @@ -107,7 +108,6 @@ class HTMLTreeBuilder {
AfterAfterFrameset,
};

bool isParsingTemplateContents() const;
bool isParsingFragmentOrTemplateContents() const;

#if ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(IOS)
Expand Down

0 comments on commit a5e967d

Please sign in to comment.