diff options
Diffstat (limited to 'dom/tests/mochitest/webcomponents')
51 files changed, 4385 insertions, 0 deletions
diff --git a/dom/tests/mochitest/webcomponents/chrome.ini b/dom/tests/mochitest/webcomponents/chrome.ini new file mode 100644 index 0000000000..8f54af95e3 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/chrome.ini @@ -0,0 +1,17 @@ +[DEFAULT] +support-files = + dummy_page.html + +[test_custom_element_ensure_custom_element.html] +[test_custom_element_htmlconstructor_chrome.html] +support-files = + htmlconstructor_autonomous_tests.js + htmlconstructor_builtin_tests.js +[test_custom_element_namespace.html] +[test_custom_element_namespace.xhtml] +[test_custom_element_upgrade_chrome.html] +support-files = + test_upgrade_page.html + upgrade_tests.js +[test_xul_custom_element.xhtml] +[test_xul_shadowdom_accesskey.xhtml] diff --git a/dom/tests/mochitest/webcomponents/dummy_page.html b/dom/tests/mochitest/webcomponents/dummy_page.html new file mode 100644 index 0000000000..fd238954c6 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/dummy_page.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<title>Dummy test page</title> +<meta charset="utf-8"/> +</head> +<body> +<p>Dummy test page</p> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/head.js b/dom/tests/mochitest/webcomponents/head.js new file mode 100644 index 0000000000..23c91d63be --- /dev/null +++ b/dom/tests/mochitest/webcomponents/head.js @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Loads an iframe. + * + * @return {Promise} promise that resolves when iframe is loaded. + */ +function createIframe(aSrcDoc) { + return new Promise(function (aResolve, aReject) { + let iframe = document.createElement("iframe"); + iframe.onload = function () { + aResolve(iframe.contentDocument); + }; + iframe.onerror = function () { + aReject("Failed to load iframe"); + }; + if (aSrcDoc) { + iframe.srcdoc = aSrcDoc; + } + document.body.appendChild(iframe); + }); +} diff --git a/dom/tests/mochitest/webcomponents/htmlconstructor_autonomous_tests.js b/dom/tests/mochitest/webcomponents/htmlconstructor_autonomous_tests.js new file mode 100644 index 0000000000..75b42389cc --- /dev/null +++ b/dom/tests/mochitest/webcomponents/htmlconstructor_autonomous_tests.js @@ -0,0 +1,107 @@ +promises.push( + test_with_new_window(testWindow => { + // Test calling the HTMLElement constructor. + (() => { + SimpleTest.doesThrow(() => { + testWindow.HTMLElement(); + }, "calling the HTMLElement constructor should throw a TypeError"); + })(); + + // Test constructing a HTMLELement. + (() => { + SimpleTest.doesThrow(() => { + new testWindow.HTMLElement(); + }, "constructing a HTMLElement should throw a TypeError"); + })(); + + // Test constructing a custom element with defining HTMLElement as entry. + (() => { + testWindow.customElements.define( + "x-defining-html-element", + testWindow.HTMLElement + ); + SimpleTest.doesThrow(() => { + new testWindow.HTMLElement(); + }, "constructing a custom element with defining HTMLElement as registry " + "entry should throw a TypeError"); + })(); + + // Test calling a custom element constructor and constructing an autonomous + // custom element. + (() => { + let num_constructor_invocations = 0; + class X extends testWindow.HTMLElement { + constructor() { + super(); + num_constructor_invocations++; + } + } + testWindow.customElements.define("x-element", X); + SimpleTest.doesThrow(() => { + X(); + }, "calling an autonomous custom element constructor should throw a TypeError"); + + let element = new X(); + SimpleTest.is( + Object.getPrototypeOf(Cu.waiveXrays(element)), + X.prototype, + "constructing an autonomous custom element; " + + "the element should be a registered constructor" + ); + SimpleTest.is( + element.localName, + "x-element", + "constructing an autonomous custom element; " + + 'the element tag name should be "x-element"' + ); + SimpleTest.is( + element.namespaceURI, + "http://www.w3.org/1999/xhtml", + "constructing an autonomous custom element; " + + "the element should be in the HTML namespace" + ); + SimpleTest.is( + element.prefix, + null, + "constructing an autonomous custom element; " + + "the element name should not have a prefix" + ); + SimpleTest.is( + element.ownerDocument, + testWindow.document, + "constructing an autonomous custom element; " + + "the element should be owned by the registry's associated " + + "document" + ); + SimpleTest.is( + num_constructor_invocations, + 1, + "constructing an autonomous custom element; " + + "the constructor should have been invoked once" + ); + })(); + + // Test if prototype is no an object. + (() => { + function ElementWithNonObjectPrototype() { + let o = Reflect.construct(testWindow.HTMLElement, [], new.target); + SimpleTest.is( + Object.getPrototypeOf(Cu.waiveXrays(o)), + window.HTMLElement.prototype, + "constructing an autonomous custom element; " + + "if prototype is not object, fallback from NewTarget's realm" + ); + } + + // Prototype have to be an object during define(), otherwise define will + // throw an TypeError exception. + ElementWithNonObjectPrototype.prototype = {}; + testWindow.customElements.define( + "x-non-object-prototype", + ElementWithNonObjectPrototype + ); + + ElementWithNonObjectPrototype.prototype = "string"; + new ElementWithNonObjectPrototype(); + })(); + }) +); diff --git a/dom/tests/mochitest/webcomponents/htmlconstructor_builtin_tests.js b/dom/tests/mochitest/webcomponents/htmlconstructor_builtin_tests.js new file mode 100644 index 0000000000..0b72ca3f7a --- /dev/null +++ b/dom/tests/mochitest/webcomponents/htmlconstructor_builtin_tests.js @@ -0,0 +1,282 @@ +[ + // [TagName, InterfaceName] + ["a", "Anchor"], + ["abbr", ""], + ["acronym", ""], + ["address", ""], + ["area", "Area"], + ["article", ""], + ["aside", ""], + ["audio", "Audio"], + ["b", ""], + ["base", "Base"], + ["basefont", ""], + ["bdo", ""], + ["big", ""], + ["blockquote", "Quote"], + ["body", "Body"], + ["br", "BR"], + ["button", "Button"], + ["canvas", "Canvas"], + ["caption", "TableCaption"], + ["center", ""], + ["cite", ""], + ["code", ""], + ["col", "TableCol"], + ["colgroup", "TableCol"], + ["data", "Data"], + ["datalist", "DataList"], + ["dd", ""], + ["del", "Mod"], + ["details", "Details"], + ["dfn", ""], + ["dir", "Directory"], + ["div", "Div"], + ["dl", "DList"], + ["dt", ""], + ["em", ""], + ["embed", "Embed"], + ["fieldset", "FieldSet"], + ["figcaption", ""], + ["figure", ""], + ["font", "Font"], + ["footer", ""], + ["form", "Form"], + ["frame", "Frame"], + ["frameset", "FrameSet"], + ["h1", "Heading"], + ["h2", "Heading"], + ["h3", "Heading"], + ["h4", "Heading"], + ["h5", "Heading"], + ["h6", "Heading"], + ["head", "Head"], + ["header", ""], + ["hgroup", ""], + ["hr", "HR"], + ["html", "Html"], + ["i", ""], + ["iframe", "IFrame"], + ["image", ""], + ["img", "Image"], + ["input", "Input"], + ["ins", "Mod"], + ["kbd", ""], + ["label", "Label"], + ["legend", "Legend"], + ["li", "LI"], + ["link", "Link"], + ["listing", "Pre"], + ["main", ""], + ["map", "Map"], + ["mark", ""], + ["marquee", "Marquee"], + ["menu", "Menu"], + ["meta", "Meta"], + ["meter", "Meter"], + ["nav", ""], + ["nobr", ""], + ["noembed", ""], + ["noframes", ""], + ["noscript", ""], + ["object", "Object"], + ["ol", "OList"], + ["optgroup", "OptGroup"], + ["option", "Option"], + ["output", "Output"], + ["p", "Paragraph"], + ["param", "Param"], + ["picture", "Picture"], + ["plaintext", ""], + ["pre", "Pre"], + ["progress", "Progress"], + ["q", "Quote"], + ["rb", ""], + ["rp", ""], + ["rt", ""], + ["rtc", ""], + ["ruby", ""], + ["s", ""], + ["samp", ""], + ["script", "Script"], + ["section", ""], + ["select", "Select"], + ["small", ""], + ["source", "Source"], + ["span", "Span"], + ["strike", ""], + ["strong", ""], + ["style", "Style"], + ["sub", ""], + ["summary", ""], + ["sup", ""], + ["table", "Table"], + ["tbody", "TableSection"], + ["td", "TableCell"], + ["textarea", "TextArea"], + ["tfoot", "TableSection"], + ["th", "TableCell"], + ["thead", "TableSection"], + ["template", "Template"], + ["time", "Time"], + ["title", "Title"], + ["tr", "TableRow"], + ["track", "Track"], + ["tt", ""], + ["u", ""], + ["ul", "UList"], + ["var", ""], + ["video", "Video"], + ["wbr", ""], + ["xmp", "Pre"], +].forEach(e => { + let tagName = e[0]; + let interfaceName = "HTML" + e[1] + "Element"; + promises.push( + test_with_new_window(testWindow => { + // Use window from iframe to isolate the test. + // Test calling the HTML*Element constructor. + (() => { + SimpleTest.doesThrow(() => { + testWindow[interfaceName](); + }, "calling the " + interfaceName + " constructor should throw a TypeError"); + })(); + + // Test constructing a HTML*ELement. + (() => { + SimpleTest.doesThrow(() => { + new testWindow[interfaceName](); + }, "constructing a " + interfaceName + " should throw a TypeError"); + })(); + + // Test constructing a custom element with defining HTML*Element as entry. + (() => { + testWindow.customElements.define( + "x-defining-" + tagName, + testWindow[interfaceName] + ); + SimpleTest.doesThrow(() => { + new testWindow[interfaceName](); + }, "constructing a custom element with defining " + interfaceName + " as registry entry should throw a TypeError"); + })(); + + // Since HTMLElement can be registered without specifying "extends", skip + // testing HTMLElement tags. + if (interfaceName !== "HTMLElement") { + // Test constructing a customized HTML*Element with defining a registry entry + // without specifying "extends". + (() => { + class X extends testWindow[interfaceName] {} + testWindow.customElements.define("x-defining-invalid-" + tagName, X); + SimpleTest.doesThrow(() => { + new X(); + }, "constructing a customized " + interfaceName + " with defining a " + 'registry entry without specifying "extends" should throw a TypeError'); + })(); + } + + // Test constructing a built-in custom element with defining a registry entry + // with incorrect "extends" information. + (() => { + class X extends testWindow[interfaceName] {} + testWindow.customElements.define("x-defining-incorrect-" + tagName, X, { + extends: tagName === "img" ? "p" : "img", + }); + SimpleTest.doesThrow(() => { + new X(); + }, "constructing a customized " + interfaceName + " with defining a " + 'registry entry with incorrect "extends" should throw a TypeError'); + })(); + + // Test calling a custom element constructor and constructing a built-in + // custom element. + (() => { + let num_constructor_invocations = 0; + class X extends testWindow[interfaceName] { + constructor() { + super(); + num_constructor_invocations++; + } + } + testWindow.customElements.define("x-" + tagName, X, { + extends: tagName, + }); + SimpleTest.doesThrow(() => { + X(); + }, "calling a customized " + interfaceName + " constructor should throw a TypeError"); + + let element = new X(); + + SimpleTest.is( + Object.getPrototypeOf(Cu.waiveXrays(element)), + X.prototype, + "constructing a customized " + + interfaceName + + "; the element should be a registered constructor" + ); + SimpleTest.is( + element.localName, + tagName, + "constructing a customized " + + interfaceName + + '; the element tag name should be "' + + tagName + + '"' + ); + SimpleTest.is( + element.namespaceURI, + "http://www.w3.org/1999/xhtml", + "constructing a customized " + + interfaceName + + "; the element should be in the HTML namespace" + ); + SimpleTest.is( + element.prefix, + null, + "constructing a customized " + + interfaceName + + "; the element name should not have a prefix" + ); + SimpleTest.is( + element.ownerDocument, + testWindow.document, + "constructing a customized " + + interfaceName + + "; the element should be owned by the registry's associated " + + "document" + ); + SimpleTest.is( + num_constructor_invocations, + 1, + "constructing a customized " + + interfaceName + + "; the constructor should have been invoked once" + ); + })(); + + // Test if prototype is no an object. + (() => { + function ElementWithNonObjectPrototype() { + let o = Reflect.construct(testWindow[interfaceName], [], new.target); + SimpleTest.is( + Object.getPrototypeOf(Cu.waiveXrays(o)), + window[interfaceName].prototype, + "constructing a customized " + + interfaceName + + "; if prototype is not object, fallback from NewTarget's realm" + ); + } + + // Prototype have to be an object during define(), otherwise define will + // throw an TypeError exception. + ElementWithNonObjectPrototype.prototype = {}; + testWindow.customElements.define( + "x-non-object-prototype-" + tagName, + ElementWithNonObjectPrototype, + { extends: tagName } + ); + + ElementWithNonObjectPrototype.prototype = "string"; + new ElementWithNonObjectPrototype(); + })(); + }) + ); +}); diff --git a/dom/tests/mochitest/webcomponents/inert_style.css b/dom/tests/mochitest/webcomponents/inert_style.css new file mode 100644 index 0000000000..3b005ede8c --- /dev/null +++ b/dom/tests/mochitest/webcomponents/inert_style.css @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This style is linked in test_shadowroot_inert_link to ensure + that link element in ShadowRoot is inert. */ +span { + padding-top: 10px; +} + diff --git a/dom/tests/mochitest/webcomponents/mochitest.ini b/dom/tests/mochitest/webcomponents/mochitest.ini new file mode 100644 index 0000000000..a268de66dc --- /dev/null +++ b/dom/tests/mochitest/webcomponents/mochitest.ini @@ -0,0 +1,50 @@ +[DEFAULT] +support-files = + inert_style.css + dummy_page.html + head.js + +[test_bug900724.html] +[test_bug1017896.html] +[test_bug1276240.html] +[test_custom_element_callback_innerhtml.html] +[test_custom_element_htmlconstructor.html] +support-files = + htmlconstructor_autonomous_tests.js + htmlconstructor_builtin_tests.js +[test_custom_element_in_shadow.html] +[test_custom_element_throw_on_dynamic_markup_insertion.html] +[test_custom_element_get.html] +[test_custom_element_when_defined.html] +[test_custom_element_uncatchable_exception.html] +skip-if = !debug # TestFunctions only applied in debug builds +[test_custom_element_upgrade.html] +support-files = + test_upgrade_page.html + upgrade_tests.js +[test_custom_element_lifecycle.html] +[test_custom_element_stack.html] +[test_custom_element_define.html] +[test_custom_element_define_parser.html] +[test_custom_element_set_element_creation_callback.html] +[test_custom_element_template.html] +[test_detached_style.html] +[test_document_adoptnode.html] +[test_document_importnode.html] +[test_event_composed.html] +[test_event_retarget.html] +[test_event_stopping.html] +[test_template.html] +[test_template_xhtml.html] +[test_shadowdom_active_pseudo_class.html] +support-files = + !/gfx/layers/apz/test/mochitest/apz_test_utils.js +[test_shadowdom_ime.html] +[test_shadowroot.html] +[test_shadowroot_clonenode.html] +[test_shadowroot_inert_element.html] +[test_shadowroot_style.html] +[test_shadowroot_style_order.html] +[test_style_fallback_content.html] +[test_link_prefetch.html] +[test_bug1269155.html] diff --git a/dom/tests/mochitest/webcomponents/test_bug1017896.html b/dom/tests/mochitest/webcomponents/test_bug1017896.html new file mode 100644 index 0000000000..53562e9251 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_bug1017896.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1017896 +--> +<head> + <title>Test template element in stale document.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1017896">Bug 1017896</a> +<div id="container"></div> +<script> + +SimpleTest.waitForExplicitFinish(); + +var frame = document.createElement("iframe"); +document.getElementById("container").appendChild(frame); + +var staleFrameDoc = frame.contentDocument; + +setTimeout(function() { + var v = staleFrameDoc.createElement("div"); + v.innerHTML = "<template>Content</template>"; + is(v.firstChild.content.childNodes.length, 1, "Template should have one child in template content."); + SimpleTest.finish(); +}, 0); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_bug1269155.html b/dom/tests/mochitest/webcomponents/test_bug1269155.html new file mode 100644 index 0000000000..6464ca92e7 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_bug1269155.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1269155 +--> +<head> + <title>Test for Bug 1269155</title> + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1269155">Mozilla Bug 1269155</a> +<p id="display"></p> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 1269155 **/ +SimpleTest.waitForExplicitFinish(); + +var content = '<div id="content" style="display: none"> </div>'; +createIframe(content) + .then((aDocument) => { + var host = aDocument.querySelector('#content'); + var root = host.attachShadow({mode: "open"}); + + var header1 = aDocument.createElement('h1'); + header1.textContent = 'Shadow Header1'; + + var paragraph1 = aDocument.createElement('p'); + paragraph1.textContent = 'shadow text paragraph1'; + + root.appendChild(header1); + root.appendChild(paragraph1); + + var root2 = paragraph1.attachShadow({mode: "open"}); + var header2 = aDocument.createElement('h2'); + header2.textContent = 'Shadow Header2'; + + var paragraph2 = aDocument.createElement('p'); + paragraph2.textContent = 'shadow text paragraph2'; + root2.appendChild(header2); + root2.appendChild(paragraph2); + + + var frag = aDocument.createDocumentFragment(); + var paragraph3 = aDocument.createElement('p'); + paragraph3.textContent = 'fragment paragraph3'; + frag.appendChild(paragraph3); + + var root3 = paragraph3.attachShadow({mode: "open"}); + var header4 = aDocument.createElement('h2'); + header4.textContent = 'Shadow Header3'; + + var paragraph4 = aDocument.createElement('p'); + paragraph4.textContent = 'shadow text paragraph4'; + + root3.appendChild(header4); + root3.appendChild(paragraph4); + + //shadow dom without compose + is(root.getRootNode(), root, "root.getRootNode() should be root."); + is(root2.getRootNode(), root2, "root2.getRootNode() should be root."); + is(root3.getRootNode(), root3, "root3.getRootNode() should be root."); + is(header1.getRootNode(), root, "header1.getRootNode() should be root."); + is(header2.getRootNode(), root2, "header1.getRootNode() should be root2."); + is(header4.getRootNode(), root3, "header1.getRootNode() should be root3."); + //shadow dom with compose + is(root.getRootNode({ composed: true }), aDocument, "root.getRootNode() with composed flag should be document."); + is(root2.getRootNode({ composed: true }), aDocument, "root2.getRootNode() with composed flag should be document."); + is(root3.getRootNode({ composed: true }), frag, "root3.getRootNode() with composed flag should be frag."); + is(header1.getRootNode({ composed: true }) , aDocument, "header1.getRootNode() with composed flag should be document."); + is(header2.getRootNode({ composed: true }) , aDocument, "header2.getRootNode() with composed flag should be document."); + is(header4.getRootNode({ composed: true }) , frag, "head4.getRootNode() with composed flag should be frag."); + //dom without compose + is(host.getRootNode(), aDocument, "host.getRootNode() should be document."); + is(header1.getRootNode(), root, "header1.getRootNode() should be root."); + is(paragraph1.getRootNode(), root, "paragraph1.getRootNode() should be root."); + is(frag.getRootNode(), frag, "frag.getRootNode() should be frag."); + //dom with compose + is(host.getRootNode({ composed: true }) , aDocument, "host.getRootNode() with composed flag should be document."); + is(header1.getRootNode({ composed: true }) , aDocument, "header1.getRootNode() with composed flag should be document."); + is(paragraph1.getRootNode({ composed: true }) , aDocument, "paragraph1.getRootNode() with composed flag should be document."); + is(frag.getRootNode({ composed: true }) , frag, "frag.getRootNode() with composed flag should be frag."); + + SimpleTest.finish(); + }); +</script> +</pre> +</body> +</html> + diff --git a/dom/tests/mochitest/webcomponents/test_bug1276240.html b/dom/tests/mochitest/webcomponents/test_bug1276240.html new file mode 100644 index 0000000000..220f005138 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_bug1276240.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1276240 +--> +<head> + <title>Test for Bug 1276240</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1276240">Mozilla Bug 1276240</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 1276240 **/ +// when passing null for the second argument, it would be ignored + +function test() { + var e = document.createElement("p", null); + is(e.getAttribute("is"), null); + + e = document.createElement("p", undefined); + is(e.getAttribute("is"), null); + + e = document.createElementNS("http://www.w3.org/1999/xhtml", "p", null); + is(e.getAttribute("is"), null); + + e = document.createElementNS("http://www.w3.org/1999/xhtml", "p", undefined); + is(e.getAttribute("is"), null); +} + +test(); + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_bug900724.html b/dom/tests/mochitest/webcomponents/test_bug900724.html new file mode 100644 index 0000000000..19abc37ac8 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_bug900724.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=900724 +--> +<head> + <title>Test for form-association in template contents.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=900724">Bug 900724</a> +<form id="formone"><template id="templateone"><input></template></form> +<form id="formthree"><template id="templatethree"></template></form> +<form id="formfive"><template id="templatefive"></template></form> +<script> +is($("formone").elements.length, 0, "Forms should have no association with controls in template contents."); + +var templateOneInput = $("templateone").content.firstChild; +is(templateOneInput.form, null, "Form controls inside template contents should not associate with forms."); + +// Try dynamically adding form/form controls using innerHTML. +$("templatethree").innerHTML = '<input>'; +is($("formthree").elements.length, 0, "Form controls inside template contents should not associate with forms."); + +// Append a form control as a child of the template (not template contents) and make sure form is associated. +var formFiveInput = document.createElement("input"); +$("templatefive").appendChild(formFiveInput); +is($("formfive").elements.length, 1, "Form control should associate with form control not in template contents."); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_callback_innerhtml.html b/dom/tests/mochitest/webcomponents/test_custom_element_callback_innerhtml.html new file mode 100644 index 0000000000..c8a8d872ed --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_callback_innerhtml.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1102502 +--> +<head> + <title>Test for connected callback for element created in the document by the parser</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1102502">Bug 1102502</a> +<div id="container"></div> + +<script> + +SimpleTest.waitForExplicitFinish(); + +var connectedCallbackCount = 0; + +class Foo extends HTMLElement { + connectedCallback() { + ok(true, "connectedCallback should be called when the parser creates an element in the document."); + connectedCallbackCount++; + // connectedCallback should be called twice, once for the element created for innerHTML and + // once for the element created in this document. + if (connectedCallbackCount == 2) { + SimpleTest.finish(); + } + } +}; + +customElements.define("x-foo", Foo); + +var container = document.getElementById("container"); +container.innerHTML = '<x-foo></x-foo>'; + +</script> + +<x-foo></x-foo> + +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_define.html b/dom/tests/mochitest/webcomponents/test_custom_element_define.html new file mode 100644 index 0000000000..7ab5ed44aa --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_define.html @@ -0,0 +1,129 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=783129 +--> +<head> + <title>Test for customElements.define</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a> +<div> +<x-unresolved id="unresolved"></x-unresolved> +</div> + +<script> + +function testDefineExtend(tag, extend, definition, expectException) { + try { + customElements.define(tag, definition, { extends: extend }); + ok(!expectException, "Defined " + tag + " extending " + extend + "."); + } catch (ex) { + ok(expectException, "Did not define " + tag + " extending " + extend + "."); + } +} + +function testDefineSimple(tag, definition, expectException) { + try { + customElements.define(tag, definition); + ok(!expectException, "Defined " + tag + " extending HTMLElement."); + } catch (ex) { + ok(expectException, "Did not define " + tag + " extending HTMLElement."); + } +} + +function startTest() { + // Test defining some simple definition. + testDefineSimple("x-html-obj-elem", class extends HTMLElement {}, false); + testDefineSimple("x-html-obj-p", class extends HTMLParagraphElement {}, false); + + // Make sure the prototype on unresolved elements is HTMLElement not HTMLUnknownElement. + var unresolved = document.getElementById("unresolved"); + is(unresolved.__proto__, HTMLElement.prototype, "Unresolved custom elements should have HTMLElement as prototype."); + + var anotherUnresolved = document.createElement("maybe-custom-element"); + is(anotherUnresolved.__proto__, HTMLElement.prototype, "Unresolved custom elements should have HTMLElement as prototype."); + + // Test defining some invalid definition. + testDefineSimple("x-invalid-number", 42, true); + testDefineSimple("x-invalid-boolean", false, true); + testDefineSimple("x-invalid-float", 1.0, true); + + // Test invalid custom element names. + testDefineSimple("invalid", class extends HTMLElement {}, true); + testDefineSimple("annotation-xml", class extends HTMLElement {}, true); + testDefineSimple("color-profile", class extends HTMLElement {}, true); + testDefineSimple("font-face", class extends HTMLElement {}, true); + testDefineSimple("font-face-src", class extends HTMLElement {}, true); + testDefineSimple("font-face-uri", class extends HTMLElement {}, true); + testDefineSimple("font-face-format", class extends HTMLElement {}, true); + testDefineSimple("font-face-name", class extends HTMLElement {}, true); + testDefineSimple("missing-glyph", class extends HTMLElement {}, true); + + // Test defining elements that extend from an existing element. + testDefineExtend("x-extend-span", "span", class extends HTMLElement {}, false); + testDefineExtend("x-extend-span-caps", "SPAN", class extends HTMLElement {}, true); + + // Test defining elements that extend from a non-existing element. + testDefineExtend("x-extend-span-nonexist", "nonexisting", class extends HTMLElement {}, true); + + // Test registration with duplicate type. + testDefineSimple("x-dupe-me", class extends HTMLElement {}, false); + testDefineSimple("x-dupe-me", class extends HTMLElement {}, true); + testDefineSimple("X-DUPE-ME", class extends HTMLElement {}, true); + testDefineExtend("x-dupe-me", "span", class extends HTMLElement {}, true); + + // customElements.define with extended type. + class ExtendButton extends HTMLButtonElement {}; + customElements.define("x-extended-button", ExtendButton, { extends: "button" }); + var extendedButton = document.createElement("button", {is: "x-extended-button"}); + is(extendedButton.tagName, "BUTTON", "Created element should have local name of BUTTON"); + is(extendedButton.__proto__, ExtendButton.prototype, "Created element should have the prototype of the extended type."); + is(extendedButton.getAttribute("is"), null, "The |is| attribute of the created element should not be the extended type."); + is(extendedButton.type, "submit", "Created element should be a button with type of \"submit\""); + + // Custom element constructor. + var constructedButton = new ExtendButton(); + is(constructedButton.tagName, "BUTTON", "Created element should have local name of BUTTON"); + is(constructedButton.__proto__, ExtendButton.prototype, "Created element should have the prototype of the extended type."); + + // Try creating an element with a custom element name, but not in the html namespace. + class XInHTMLNamespace extends HTMLElement {}; + customElements.define("x-in-html-namespace", XInHTMLNamespace); + var wrongNamespaceElem = document.createElementNS("http://www.w3.org/2000/svg", "x-in-html-namespace"); + isnot(wrongNamespaceElem.__proto__, XInHTMLNamespace.prototype, "Definition for element in html namespace should not apply to SVG elements."); + + var div = document.createElement("div"); + div.appendChild(extendedButton); + is(div.innerHTML, '<button is="x-extended-button"></button>', "'is value' should be serialized."); + + const de = SpecialPowers.Ci.nsIDocumentEncoder; + var htmlencoder = SpecialPowers.Cu.createDocumentEncoder("text/html"); + htmlencoder.init(document, "text/html", de.OutputLFLineBreak); + htmlencoder.setCharset("UTF-8"); + htmlencoder.setContainerNode(div); + is(htmlencoder.encodeToString(), '<button is="x-extended-button"></button>', + "'is value' should be serialized (html)."); + + var xhtmlencoder = SpecialPowers.Cu.createDocumentEncoder("application/xhtml+xml"); + xhtmlencoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak); + xhtmlencoder.setCharset("UTF-8"); + xhtmlencoder.setContainerNode(div); + is(xhtmlencoder.encodeToString(), '<button is="x-extended-button" xmlns="http://www.w3.org/1999/xhtml"></button>', + "'is value' should be serialized (xhtml)."); + + var xmlencoder = SpecialPowers.Cu.createDocumentEncoder("text/xml"); + xmlencoder.init(document, "text/xml", de.OutputLFLineBreak); + xmlencoder.setCharset("UTF-8"); + xmlencoder.setContainerNode(div); + is(xmlencoder.encodeToString(), '<button is="x-extended-button" xmlns="http://www.w3.org/1999/xhtml"></button>', + "'is value' should be serialized (xml)."); +} + +startTest(); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_define_parser.html b/dom/tests/mochitest/webcomponents/test_custom_element_define_parser.html new file mode 100644 index 0000000000..dc247220d4 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_define_parser.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=783129 +--> +<head> + <title>Test for customElements.define for elements created by the parser</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +<script> + +var uncaughtError; +window.onerror = function(message, url, lineNumber, columnNumber, error) { + uncaughtError = error; +}; + +var isConnectedCallbackCalled = false; +class XButton extends HTMLButtonElement { + connectedCallback() { + ok(!isConnectedCallbackCalled, "ConnectedCallback should only be called once."); + is(this.tagName, "BUTTON", "Only the <button> element should be upgraded."); + isConnectedCallbackCalled = true; + } +}; + +customElements.define("x-button", XButton, { extends: "button" }); + +class XDiv extends HTMLDivElement { + constructor() { + // Queue a task to check error and callbacks. + setTimeout(() => { + ok(isConnectedCallbackCalled, "ConnectedCallback should be called."); + ok(uncaughtError instanceof TypeError, + "TypeError should be filed for upgrading <x-div> element."); + SimpleTest.finish(); + }, 0); + super(); + } + + connectedCallback() { + ok(false, "Connected callback for x-div should not be called."); + } +}; + +customElements.define("x-div", XDiv); + +SimpleTest.waitForExplicitFinish(); + +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a> +<button is="x-button"></button><!-- should be upgraded --> +<x-button></x-button><!-- should not be upgraded --> +<span is="x-button"></span><!-- should not be upgraded --> +<div is="x-div"></div><!-- should not be upgraded --> +<x-div></x-div><!-- should be upgraded, but failed --> +<script> +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_ensure_custom_element.html b/dom/tests/mochitest/webcomponents/test_custom_element_ensure_custom_element.html new file mode 100644 index 0000000000..03ed2e3815 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_ensure_custom_element.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html lang="en"> + <!-- + https://bugzilla.mozilla.org/show_bug.cgi?id=1813077 + --> +<head> + <meta charset="utf-8"> + <title>Test for customElements.ensureCustomElement</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script> + /* + Note: once you import one of these custom elements, they stay on the window + outside of the task that imported them. This can create issues if writing + another test in this file. + */ + add_task(async function test_ensure_custom_elements() { + let registry = SpecialPowers.wrap(customElements); + ok(window.ensureCustomElements, "should be defined"); + + // Ensure the custom elements from ESModules are not defined. + is(registry.get("moz-button-group"), undefined, "moz-button-group should be undefined since we have not yet imported it"); + is(registry.get("moz-support-link"), undefined, "moz-support-link should be undefined since we have not yet imported it"); + is(registry.get("moz-toggle"), undefined, "moz-toggle should be undefined since we have not yet imported it"); + + // Import a single custom element and assert it exists in the custom + // element registry + let modules = await window.ensureCustomElements("moz-support-link"); + ok(registry.get("moz-support-link"), "moz-support-link should be defined after importing it"); + is(modules, null, "There should not be a return value when using ensureCustomElements"); + + // Import multiple custom elements and assert they exist in the registry + modules = undefined; + is(registry.get("moz-button-group"), undefined, "moz-button-group should be undefined since we have not yet imported it"); + is(registry.get("moz-toggle"), undefined, "moz-toggle should be undefined since we have not yet imported it") + modules = await window.ensureCustomElements("moz-toggle", "moz-button-group"); + is(modules, null, "There should not be a return value when using ensureCustomElements"); + ok(registry.get("moz-toggle"), "moz-toggle should be defined after importing it"); + ok(registry.get("moz-button-group"), "moz-button-group should be defined after importing it"); + + // Ensure there are no errors if the imported elements are imported + // again + modules = undefined; + modules = await window.ensureCustomElements("moz-support-link", "moz-toggle", "moz-button-group"); + ok(true, "The custom elements should not throw an error if imported again"); + is(modules, null, "There should not be a return value when using ensureCustomElements"); + }) + </script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_get.html b/dom/tests/mochitest/webcomponents/test_custom_element_get.html new file mode 100644 index 0000000000..0688d21aaa --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_get.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1275838 +--> +<head> + <title>Test custom elements get function.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe id="iframe"></iframe> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1275838">Bug 1275838</a> +<script> +const testWindow = iframe.contentDocument.defaultView; +const customElements = testWindow.customElements; + +ok('customElements' in testWindow, '"window.customElements" exists'); +ok('define' in customElements, '"window.customElements.define" exists'); +ok('get' in customElements, '"window.customElements.get" exists'); + +const name = 'test-get-existing'; +class C extends HTMLElement {}; +customElements.define(name, C); +is(customElements.get(name), C, 'get() returns the constructor'); +is(customElements.get('test-get-not-defined'), undefined, + 'get() returns undefined for not-defined name'); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_htmlconstructor.html b/dom/tests/mochitest/webcomponents/test_custom_element_htmlconstructor.html new file mode 100644 index 0000000000..c295a15038 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_htmlconstructor.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1274159 +--> +<head> + <title>Test HTMLConstructor for custom elements.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1274159">Bug 1274159</a> +<script type="text/javascript"> +function test_with_new_window(f) { + return new Promise((aResolve) => { + let iframe = document.createElement('iframe'); + iframe.setAttribute('type', 'content'); + iframe.setAttribute('src', 'dummy_page.html'); + iframe.onload = function() { + f(iframe.contentWindow); + aResolve(); + }; + document.body.appendChild(iframe); + }); +} + +// Fake the Cu.waiveXrays, so that we can share the tests with mochitest chrome. +var Cu = { waiveXrays: (obj) => obj }; +var promises = []; +SimpleTest.waitForExplicitFinish(); +</script> +<!-- Test cases for autonomous element --> +<script type="text/javascript" src="htmlconstructor_autonomous_tests.js"></script> +<!-- Test cases for customized built-in element --> +<script type="text/javascript" src="htmlconstructor_builtin_tests.js"></script> +<script type="text/javascript"> +Promise.all(promises).then(() => { + SimpleTest.finish(); +}); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_htmlconstructor_chrome.html b/dom/tests/mochitest/webcomponents/test_custom_element_htmlconstructor_chrome.html new file mode 100644 index 0000000000..2adb0aac28 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_htmlconstructor_chrome.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1274159 +--> +<head> + <title>Test HTMLConstructor for custom elements.</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1274159">Bug 1274159</a> +<script type="text/javascript"> +function test_with_new_window(f) { + return new Promise((aResolve) => { + let iframe = document.createElement('iframe'); + iframe.setAttribute('type', 'content'); + iframe.setAttribute('src', 'http://example.org/tests/dom/tests/mochitest/webcomponents/dummy_page.html'); + iframe.onload = function() { + f(iframe.contentWindow); + aResolve(); + }; + document.body.appendChild(iframe); + }); +} + +var promises = []; +SimpleTest.waitForExplicitFinish(); +</script> +<!-- Test cases for autonomous element --> +<script type="text/javascript" src="htmlconstructor_autonomous_tests.js"></script> +<!-- Test cases for customized built-in element --> +<script type="text/javascript" src="htmlconstructor_builtin_tests.js"></script> +<script type="text/javascript"> +Promise.all(promises).then(() => { + SimpleTest.finish(); +}); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_in_shadow.html b/dom/tests/mochitest/webcomponents/test_custom_element_in_shadow.html new file mode 100644 index 0000000000..4371d296ce --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_in_shadow.html @@ -0,0 +1,129 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1087460 +--> +<head> + <title>Test for custom element callbacks in shadow DOM.</title> + <script type="text/javascript" src="head.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1087460">Bug 1087460</a> + +<script> + +SimpleTest.waitForExplicitFinish(); + +var content = '<div id="container"></div>'; +createIframe(content) + .then((aDocument) => { + + // Test callback for custom element when used after registration. + + var iframeWin = aDocument.defaultView; + var connectedCallbackCount = 0; + var disconnectedCallbackCount = 0; + var attributeChangedCallbackCount = 0; + + class Foo extends iframeWin.HTMLElement + { + connectedCallback() { + connectedCallbackCount++; + } + + disconnectedCallback() { + disconnectedCallbackCount++; + } + + attributeChangedCallback(aName, aOldValue, aNewValue) { + attributeChangedCallbackCount++; + } + + static get observedAttributes() { + return ["data-foo"]; + } + } + + iframeWin.customElements.define("x-foo", Foo); + + var container = aDocument.getElementById("container"); + var shadow = container.attachShadow({mode: "open"}); + var customElem = aDocument.createElement("x-foo"); + + is(attributeChangedCallbackCount, 0, "attributeChangedCallback should not be called after just creating an element."); + customElem.setAttribute("data-foo", "bar"); + is(attributeChangedCallbackCount, 1, "attributeChangedCallback should be called after setting an attribute."); + + is(connectedCallbackCount, 0, "connectedCallback should not be called on an element that is not in a document/composed document."); + shadow.appendChild(customElem); + is(connectedCallbackCount, 1, "connectedCallback should be called after attaching custom element to the composed document."); + + is(disconnectedCallbackCount, 0, "disconnectedCallback should not be called without detaching custom element."); + shadow.removeChild(customElem); + is(disconnectedCallbackCount, 1, "disconnectedCallback should be called after detaching custom element from the composed document."); + + // Test callback for custom element already in the composed doc when created. + + connectedCallbackCount = 0; + disconnectedCallbackCount = 0; + attributeChangedCallbackCount = 0; + + shadow.innerHTML = "<x-foo></x-foo>"; + is(connectedCallbackCount, 1, "connectedCallback should be called after creating an element in the composed document."); + + shadow.innerHTML = ""; + is(disconnectedCallbackCount, 1, "disconnectedCallback should be called after detaching custom element from the composed document."); + + // Test callback for custom element in shadow DOM when host attached/detached to/from document. + + connectedCallbackCount = 0; + disconnectedCallbackCount = 0; + attributeChangedCallbackCount = 0; + + var host = aDocument.createElement("div"); + shadow = host.attachShadow({mode: "open"}); + customElem = aDocument.createElement("x-foo"); + + is(connectedCallbackCount, 0, "connectedCallback should not be called on newly created element."); + shadow.appendChild(customElem); + is(connectedCallbackCount, 0, "connectedCallback should not be called on attaching to a tree that is not in the composed document."); + + is(disconnectedCallbackCount, 0, "disconnectedCallback should not be called."); + shadow.removeChild(customElem); + is(disconnectedCallbackCount, 0, "disconnectedCallback should not be called when detaching from a tree that is not in the composed document."); + + shadow.appendChild(customElem); + is(connectedCallbackCount, 0, "connectedCallback should still not be called after reattaching to a shadow tree that is not in the composed document."); + + container.appendChild(host); + is(connectedCallbackCount, 1, "connectedCallback should be called after host is inserted into document."); + + container.removeChild(host); + is(disconnectedCallbackCount, 1, "disconnectedCallback should be called after host is removed from document."); + + // Test callback for custom element for upgraded element. + + connectedCallbackCount = 0; + disconnectedCallbackCount = 0; + attributeChangedCallbackCount = 0; + + shadow = container.shadowRoot; + shadow.innerHTML = "<x-bar></x-bar>"; + + class Bar extends iframeWin.HTMLElement { + connectedCallback() { + connectedCallbackCount++; + } + }; + + iframeWin.customElements.define("x-bar", Bar); + is(connectedCallbackCount, 1, "connectedCallback should be called after upgrading element in composed document."); + + SimpleTest.finish(); + }); +</script> + +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html b/dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html new file mode 100644 index 0000000000..a02d0b297b --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html @@ -0,0 +1,432 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=783129 +--> +<head> + <title>Test for custom elements lifecycle callback</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a> +<div id="container"> + <x-hello id="hello"></x-hello> + <button id="extbutton" is="x-button"></button> +</div> +<script> + +var container = document.getElementById("container"); + +// Tests callbacks after defining element type that is already in the document. +// create element in document -> define -> remove from document +function testRegisterUnresolved() { + var helloElem = document.getElementById("hello"); + + var connectedCallbackCalled = false; + var disconnectedCallbackCalled = false; + + class Hello extends HTMLElement { + connectedCallback() { + is(connectedCallbackCalled, false, "Connected callback should only be called once in this test."); + is(this, helloElem, "The 'this' value should be the custom element."); + connectedCallbackCalled = true; + } + + disconnectedCallback() { + is(connectedCallbackCalled, true, "Connected callback should be called before detached"); + is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test."); + disconnectedCallbackCalled = true; + is(this, helloElem, "The 'this' value should be the custom element."); + runNextTest(); + } + + attributeChangedCallback(name, oldValue, newValue) { + ok(false, "attributeChanged callback should never be called in this test."); + } + }; + + customElements.define("x-hello", Hello); + + // Remove element from document to trigger disconnected callback. + container.removeChild(helloElem); +} + +// Tests callbacks after defining an extended element type that is already in the document. +// create element in document -> define -> remove from document +function testRegisterUnresolvedExtended() { + var buttonElem = document.getElementById("extbutton"); + + var connectedCallbackCalled = false; + var disconnectedCallbackCalled = false; + + class XButton extends HTMLButtonElement { + connectedCallback() { + is(connectedCallbackCalled, false, "Connected callback should only be called once in this test."); + is(this, buttonElem, "The 'this' value should be the custom element."); + connectedCallbackCalled = true; + } + + disconnectedCallback() { + is(connectedCallbackCalled, true, "Connected callback should be called before detached"); + is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test."); + disconnectedCallbackCalled = true; + is(this, buttonElem, "The 'this' value should be the custom element."); + runNextTest(); + } + + attributeChangedCallback(name, oldValue, newValue) { + ok(false, "attributeChanged callback should never be called in this test."); + } + }; + + customElements.define("x-button", XButton, { extends: "button" }); + + // Remove element from document to trigger disconnected callback. + container.removeChild(buttonElem); +} + +function testInnerHTML() { + var connectedCallbackCalled = false; + + class XInnerHTML extends HTMLElement { + connectedCallback() { + is(connectedCallbackCalled, false, "Connected callback should only be called once in this test."); + connectedCallbackCalled = true; + } + }; + + customElements.define("x-inner-html", XInnerHTML); + var div = document.createElement(div); + document.documentElement.appendChild(div); + div.innerHTML = '<x-inner-html></x-inner-html>'; + is(connectedCallbackCalled, true, "Connected callback should be called after setting innerHTML."); + runNextTest(); +} + +function testInnerHTMLExtended() { + var connectedCallbackCalled = false; + + class XInnerHTMLExtend extends HTMLButtonElement { + connectedCallback() { + is(connectedCallbackCalled, false, "Connected callback should only be called once in this test."); + connectedCallbackCalled = true; + } + }; + + customElements.define("x-inner-html-extended", XInnerHTMLExtend, { extends: "button" }); + var div = document.createElement(div); + document.documentElement.appendChild(div); + div.innerHTML = '<button is="x-inner-html-extended"></button>'; + is(connectedCallbackCalled, true, "Connected callback should be called after setting innerHTML."); + runNextTest(); +} + +function testInnerHTMLUpgrade() { + var connectedCallbackCalled = false; + + var div = document.createElement(div); + document.documentElement.appendChild(div); + div.innerHTML = '<x-inner-html-upgrade></x-inner-html-upgrade>'; + + class XInnerHTMLUpgrade extends HTMLElement { + connectedCallback() { + is(connectedCallbackCalled, false, "Connected callback should only be called once in this test."); + connectedCallbackCalled = true; + } + }; + + customElements.define("x-inner-html-upgrade", XInnerHTMLUpgrade); + is(connectedCallbackCalled, true, "Connected callback should be called after registering."); + runNextTest(); +} + +function testInnerHTMLExtendedUpgrade() { + var connectedCallbackCalled = false; + + var div = document.createElement(div); + document.documentElement.appendChild(div); + div.innerHTML = '<button is="x-inner-html-extended-upgrade"></button>'; + + class XInnerHTMLExtnedUpgrade extends HTMLButtonElement { + connectedCallback() { + is(connectedCallbackCalled, false, "Connected callback should only be called once in this test."); + connectedCallbackCalled = true; + } + }; + + customElements.define("x-inner-html-extended-upgrade", XInnerHTMLExtnedUpgrade, { extends: "button" }); + is(connectedCallbackCalled, true, "Connected callback should be called after registering."); + runNextTest(); +} + +// Test callback when creating element after defining an element type. +// define -> create element -> insert into document -> remove from document +function testRegisterResolved() { + var connectedCallbackCalled = false; + var disconnectedCallbackCalled = false; + + class Resolved extends HTMLElement { + connectedCallback() { + is(connectedCallbackCalled, false, "Connected callback should only be called on in this test."); + is(this, createdElement, "The 'this' value should be the custom element."); + connectedCallbackCalled = true; + } + + disconnectedCallback() { + is(connectedCallbackCalled, true, "Connected callback should be called before detached"); + is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test."); + is(this, createdElement, "The 'this' value should be the custom element."); + disconnectedCallbackCalled = true; + runNextTest(); + } + + attributeChangedCallback() { + ok(false, "attributeChanged callback should never be called in this test."); + } + }; + + customElements.define("x-resolved", Resolved); + + var createdElement = document.createElement("x-resolved"); + is(createdElement.__proto__, Resolved.prototype, "Prototype of custom element should be the defined prototype."); + + // Insert element into document to trigger attached callback. + container.appendChild(createdElement); + + // Remove element from document to trigger detached callback. + container.removeChild(createdElement); +} + +// Callbacks should always be the same ones when registered. +function testChangingCallback() { + var callbackCalled = false; + + class TestCallback extends HTMLElement + { + attributeChangedCallback(aName, aOldValue, aNewValue) { + is(callbackCalled, false, "Callback should only be called once in this test."); + callbackCalled = true; + runNextTest(); + } + + static get observedAttributes() { + return ["data-foo"]; + } + } + + customElements.define("x-test-callback", TestCallback); + + TestCallback.prototype.attributeChangedCallback = function(name, oldValue, newValue) { + ok(false, "Only callbacks at registration should be called."); + }; + + var elem = document.createElement("x-test-callback"); + elem.setAttribute("data-foo", "bar"); +} + +function testAttributeChanged() { + var createdElement; + // Sequence of callback arguments that we expect from attribute changed callback + // after changing attributes values in a specific order. + var expectedCallbackArguments = [ + // [oldValue, newValue] + [null, "newvalue"], // Setting the attribute value to "newvalue" + ["newvalue", "nextvalue"], // Changing the attribute value from "newvalue" to "nextvalue" + ["nextvalue", ""], // Changing the attribute value from "nextvalue" to empty string + ["", null], // Removing the attribute. + ]; + + class AttrChange extends HTMLElement + { + attributeChangedCallback(name, oldValue, newValue) { + is(this, createdElement, "The 'this' value should be the custom element."); + ok(expectedCallbackArguments.length, "Attribute changed callback should not be called more than expected."); + + is(name, "changeme", "name arugment in attribute changed callback should be the name of the changed attribute."); + + var expectedArgs = expectedCallbackArguments.shift(); + is(oldValue, expectedArgs[0], "The old value argument should match the expected value."); + is(newValue, expectedArgs[1], "The new value argument should match the expected value."); + + if (expectedCallbackArguments.length === 0) { + // Done with the attribute changed callback test. + runNextTest(); + } + } + + static get observedAttributes() { + return ["changeme"]; + } + } + + customElements.define("x-attrchange", AttrChange); + + createdElement = document.createElement("x-attrchange"); + createdElement.setAttribute("changeme", "newvalue"); + createdElement.setAttribute("changeme", "nextvalue"); + createdElement.setAttribute("changeme", ""); + createdElement.removeAttribute("changeme"); +} + +function testAttributeChangedExtended() { + var callbackCalled = false; + + class ExtnededAttributeChange extends HTMLButtonElement + { + attributeChangedCallback(name, oldValue, newValue) { + is(callbackCalled, false, "Callback should only be called once in this test."); + callbackCalled = true; + runNextTest(); + } + + static get observedAttributes() { + return ["foo"]; + } + } + + customElements.define("x-extended-attribute-change", ExtnededAttributeChange, + { extends: "button" }); + + var elem = document.createElement("button", {is: "x-extended-attribute-change"}); + elem.setAttribute("foo", "bar"); +} + +function testStyleAttributeChange() { + var expectedCallbackArguments = [ + // [name, oldValue, newValue] + ["style", null, "font-size: 10px;"], + ["style", "font-size: 10px;", "font-size: 20px;"], + ["style", "font-size: 20px;", "font-size: 30px;"], + ]; + + customElements.define("x-style-attribute-change", class extends HTMLElement { + attributeChangedCallback(name, oldValue, newValue) { + if (expectedCallbackArguments.length === 0) { + ok(false, "Got unexpected attributeChangedCallback?"); + return; + } + + let expectedArgument = expectedCallbackArguments.shift(); + is(name, expectedArgument[0], + "The name argument should match the expected value."); + is(oldValue, expectedArgument[1], + "The old value argument should match the expected value."); + is(newValue, expectedArgument[2], + "The new value argument should match the expected value."); + } + + static get observedAttributes() { + return ["style"]; + } + }); + + var elem = document.createElement("x-style-attribute-change"); + elem.style.fontSize = "10px"; + elem.style.fontSize = "20px"; + elem.style.fontSize = "30px"; + + ok(expectedCallbackArguments.length === 0, + "The attributeChangedCallback should be fired synchronously."); + runNextTest(); +} + +// Creates a custom element that is an upgrade candidate (no registration) +// and mutate the element in ways that would call callbacks for registered +// elements. +function testUpgradeCandidate() { + var createdElement = document.createElement("x-upgrade-candidate"); + container.appendChild(createdElement); + createdElement.setAttribute("foo", "bar"); + container.removeChild(createdElement); + ok(true, "Nothing bad should happen when trying to mutating upgrade candidates."); + runNextTest(); +} + +function testNotInDocEnterLeave() { + class DestinedForFragment extends HTMLElement { + connectedCallback() { + ok(false, "Connected callback should not be called."); + } + + disconnectedCallback() { + ok(false, "Disconnected callback should not be called."); + } + }; + + var createdElement = document.createElement("x-destined-for-fragment"); + + customElements.define("x-destined-for-fragment", DestinedForFragment); + + var fragment = new DocumentFragment(); + fragment.appendChild(createdElement); + fragment.removeChild(createdElement); + + var divNotInDoc = document.createElement("div"); + divNotInDoc.appendChild(createdElement); + divNotInDoc.removeChild(createdElement); + + runNextTest(); +} + +function testEnterLeaveView() { + var connectedCallbackCalled = false; + var disconnectedCallbackCalled = false; + + class ElementInDiv extends HTMLElement { + connectedCallback() { + is(connectedCallbackCalled, false, "Connected callback should only be called on in this test."); + connectedCallbackCalled = true; + } + + disconnectedCallback() { + is(connectedCallbackCalled, true, "Connected callback should be called before detached"); + is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test."); + disconnectedCallbackCalled = true; + runNextTest(); + } + }; + + var div = document.createElement("div"); + customElements.define("x-element-in-div", ElementInDiv); + var customElement = document.createElement("x-element-in-div"); + div.appendChild(customElement); + is(connectedCallbackCalled, false, "Appending a custom element to a node that is not in the document should not call the connected callback."); + + container.appendChild(div); + container.removeChild(div); +} + +var testFunctions = [ + testRegisterUnresolved, + testRegisterUnresolvedExtended, + testInnerHTML, + testInnerHTMLExtended, + testInnerHTMLUpgrade, + testInnerHTMLExtendedUpgrade, + testRegisterResolved, + testAttributeChanged, + testAttributeChangedExtended, + testStyleAttributeChange, + testUpgradeCandidate, + testChangingCallback, + testNotInDocEnterLeave, + testEnterLeaveView, + SimpleTest.finish +]; + +function runNextTest() { + if (testFunctions.length) { + var nextTestFunction = testFunctions.shift(); + info(`Start ${nextTestFunction.name} ...`); + nextTestFunction(); + } +} + +SimpleTest.waitForExplicitFinish(); + +runNextTest(); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_namespace.html b/dom/tests/mochitest/webcomponents/test_custom_element_namespace.html new file mode 100644 index 0000000000..a4dc4c40cc --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_namespace.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Custom Elements in an HTML document</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script> + SimpleTest.waitForExplicitFinish(); + + const HTML_NS = "http://www.w3.org/1999/xhtml"; + const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + + class TestXULCustomElement extends XULElement { + constructor() { + super(); + } + + get connected() { + return true; + } + } + + customElements.define("test-xul-element", TestXULCustomElement); + + class TestHTMLCustomElement extends HTMLElement { + constructor() { + super(); + } + + get connected() { + return true; + } + } + + customElements.define("test-html-element", TestHTMLCustomElement); + + function checkElement(element, ns, connected, type) { + is(element.namespaceURI, ns, `${type} should have the correct namespace`); + if (connected) { + ok(element.connected, `${type} should have applied the class`); + } else { + is(element.connected, undefined, `${type} should not have applied the class`); + } + } + + function runTest() { + let element = new TestXULCustomElement(); + checkElement(element, XUL_NS, true, "instantiated XUL"); + + element = document.getElementById("xul2"); + checkElement(element, HTML_NS, false, "parsed XUL as HTML"); + + element = document.createElement("test-xul-element"); + checkElement(element, HTML_NS, false, "document.createElement(XUL)"); + + element = document.createXULElement("test-xul-element"); + checkElement(element, XUL_NS, true, "document.createXULElement(XUL)"); + + element = document.createElementNS(XUL_NS, "test-xul-element"); + checkElement(element, XUL_NS, true, "document.createElementNS(XUL, XUL)"); + + element = document.createElementNS(HTML_NS, "test-xul-element"); + checkElement(element, HTML_NS, false, "document.createElementNS(HTML, XUL)"); + + element = new TestHTMLCustomElement(); + checkElement(element, HTML_NS, true, "instantiated HTML"); + + element = document.getElementById("html2"); + checkElement(element, HTML_NS, true, "parsed HTML as HTML"); + + element = document.createElement("test-html-element"); + checkElement(element, HTML_NS, true, "document.createElement(HTML)"); + + element = document.createXULElement("test-html-element"); + checkElement(element, XUL_NS, false, "document.createXULElement(HTML)"); + + element = document.createElementNS(XUL_NS, "test-html-element"); + checkElement(element, XUL_NS, false, "document.createElementNS(XUL, HTML)"); + + element = document.createElementNS(HTML_NS, "test-html-element"); + checkElement(element, HTML_NS, true, "document.createElementNS(HTML, HTML)"); + + SimpleTest.finish(); + } + </script> +</head> +<body onload="runTest();"> + <p id="display"></p> + <div id="content"> + <test-xul-element id="xul2"/> + <test-html-element id="html2"/> + </div> + <pre id="test"></pre> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_namespace.xhtml b/dom/tests/mochitest/webcomponents/test_custom_element_namespace.xhtml new file mode 100644 index 0000000000..5bfdd779ad --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_namespace.xhtml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Custom Elements in an XHTML document</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script> + SimpleTest.waitForExplicitFinish(); + + const HTML_NS = "http://www.w3.org/1999/xhtml"; + const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + + class TestXULCustomElement extends XULElement { + constructor() { + super(); + } + + get connected() { + return true; + } + } + + customElements.define("test-xul-element", TestXULCustomElement); + + class TestHTMLCustomElement extends HTMLElement { + constructor() { + super(); + } + + get connected() { + return true; + } + } + + customElements.define("test-html-element", TestHTMLCustomElement); + + function checkElement(element, ns, connected, type) { + is(element.namespaceURI, ns, `${type} should have the correct namespace`); + if (connected) { + ok(element.connected, `${type} should have applied the class`); + } else { + is(element.connected, undefined, `${type} should not have applied the class`); + } + } + + function runTest() { + let element = new TestXULCustomElement(); + checkElement(element, XUL_NS, true, "instantiated XUL"); + + element = document.getElementById("xul1"); + checkElement(element, XUL_NS, true, "parsed XUL as XUL"); + + element = document.getElementById("xul2"); + checkElement(element, HTML_NS, false, "parsed XUL as HTML"); + + element = document.createElement("test-xul-element"); + checkElement(element, HTML_NS, false, "document.createElement(XUL)"); + + element = document.createXULElement("test-xul-element"); + checkElement(element, XUL_NS, true, "document.createXULElement(XUL)"); + + element = document.createElementNS(XUL_NS, "test-xul-element"); + checkElement(element, XUL_NS, true, "document.createElementNS(XUL, XUL)"); + + element = document.createElementNS(HTML_NS, "test-xul-element"); + checkElement(element, HTML_NS, false, "document.createElementNS(HTML, XUL)"); + + element = new TestHTMLCustomElement(); + checkElement(element, HTML_NS, true, "instantiated HTML"); + + element = document.getElementById("html1"); + checkElement(element, XUL_NS, false, "parsed HTML as XUL"); + + element = document.getElementById("html2"); + checkElement(element, HTML_NS, true, "parsed HTML as HTML"); + + element = document.createElement("test-html-element"); + checkElement(element, HTML_NS, true, "document.createElement(HTML)"); + + element = document.createXULElement("test-html-element"); + checkElement(element, XUL_NS, false, "document.createXULElement(HTML)"); + + element = document.createElementNS(XUL_NS, "test-html-element"); + checkElement(element, XUL_NS, false, "document.createElementNS(XUL, HTML)"); + + element = document.createElementNS(HTML_NS, "test-html-element"); + checkElement(element, HTML_NS, true, "document.createElementNS(HTML, HTML)"); + + SimpleTest.finish(); + } + </script> +</head> +<body onload="runTest();"> + <p id="display"></p> + <div id="content"> + <test-xul-element xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="xul1"/> + <test-html-element xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="html1"/> + <test-xul-element id="xul2"/> + <test-html-element id="html2"/> + </div> + <pre id="test"></pre> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_set_element_creation_callback.html b/dom/tests/mochitest/webcomponents/test_custom_element_set_element_creation_callback.html new file mode 100644 index 0000000000..6a8f127d93 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_set_element_creation_callback.html @@ -0,0 +1,159 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1460815 +--> +<head> + <title>Test for customElements.setElementCreationCallback</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1460815">Bug 1460815</a> +<div> +</div> + +<script> + +let registry = SpecialPowers.wrap(customElements); + +function simpleTest() { + let callbackCalled = false; + class XObjElement extends HTMLElement {}; + registry.setElementCreationCallback("x-html-obj-elem", (type) => { + if (callbackCalled) { + ok(false, "Callback should not be invoked more than once."); + } + callbackCalled = true; + is(type, "x-html-obj-elem", "Type is passed to the callback."); + customElements.define("x-html-obj-elem", XObjElement); + }); + ok(!callbackCalled, "Callback should not be called."); + let el = document.createElement("x-html-obj-elem"); + ok(callbackCalled, "Callback should be called."); + is(Object.getPrototypeOf(el), XObjElement.prototype, "Created element should have the prototype of the custom type."); +} + +function multipleDefinitionTest() { + let callbackCalled = false; + class XObjElement1 extends HTMLElement {}; + class XObjElement2 extends HTMLElement {}; + let callback = (type) => { + if (callbackCalled) { + ok(false, "Callback should not be invoked more than once."); + } + callbackCalled = true; + is(type, "x-html-obj-elem1", "Type is passed to the callback."); + customElements.define("x-html-obj-elem1", XObjElement1); + customElements.define("x-html-obj-elem2", XObjElement2); + }; + registry.setElementCreationCallback("x-html-obj-elem1", callback); + registry.setElementCreationCallback("x-html-obj-elem2", callback); + ok(!callbackCalled, "Callback should not be called."); + let el1 = document.createElement("x-html-obj-elem1"); + ok(callbackCalled, "Callback should be called."); + is(Object.getPrototypeOf(el1), XObjElement1.prototype, "Created element should have the prototype of the custom type."); + let el2 = document.createElement("x-html-obj-elem2"); + is(Object.getPrototypeOf(el2), XObjElement2.prototype, "Created element should have the prototype of the custom type."); +} + +function throwIfDefined() { + let callbackCalled = false; + class XObjElement3 extends HTMLElement {}; + customElements.define("x-html-obj-elem3", XObjElement3); + try { + registry.setElementCreationCallback( + "x-html-obj-elem3", () => callbackCalled = true); + } catch (e) { + ok(true, "Set a callback on defined type should throw."); + } + ok(!callbackCalled, "Callback should not be called."); +} + +function throwIfSetTwice() { + let callbackCalled = false; + registry.setElementCreationCallback( + "x-html-obj-elem4", () => callbackCalled = true); + try { + registry.setElementCreationCallback( + "x-html-obj-elem4", () => callbackCalled = true); + } catch (e) { + ok(true, "Callack shouldn't be set twice on the same type."); + } + ok(!callbackCalled, "Callback should not be called."); +} + +function simpleExtendedTest() { + let callbackCalled = false; + class ExtendButton extends HTMLButtonElement {}; + registry.setElementCreationCallback("x-extended-button", (type) => { + if (callbackCalled) { + ok(false, "Callback should not be invoked more than once."); + } + callbackCalled = true; + customElements.define("x-extended-button", ExtendButton, { extends: "button" }); + is(type, "x-extended-button", "Type is passed to the callback."); + }); + ok(!callbackCalled, "Callback should not be called."); + let el = document.createElement("button", { is: "x-extended-button"}); + ok(callbackCalled, "Callback should be called."); + is(Object.getPrototypeOf(el), ExtendButton.prototype, "Created element should have the prototype of the extended type."); + is(el.getAttribute("is"), null, "The |is| attribute of the created element should not be the extended type."); +} + +function simpleInnerHTMLTest() { + let callbackCalled = false; + class XObjElement4 extends HTMLElement {}; + registry.setElementCreationCallback("x-html-obj-elem5", (type) => { + if (callbackCalled) { + ok(false, "Callback should not be invoked more than once."); + } + callbackCalled = true; + is(type, "x-html-obj-elem5", "Type is passed to the callback."); + customElements.define("x-html-obj-elem5", XObjElement4); + }); + ok(!callbackCalled, "Callback should not be called."); + let p = document.createElement("p"); + p.innerHTML = "<x-html-obj-elem5></x-html-obj-elem5>"; + let el = p.firstChild; + ok(callbackCalled, "Callback should be called."); + is(Object.getPrototypeOf(el), XObjElement4.prototype, "Created element should have the prototype of the custom type."); +} + +function twoElementInnerHTMLTest() { + let callbackCalled = false; + class XObjElement5 extends HTMLElement {}; + registry.setElementCreationCallback("x-html-obj-elem6", (type) => { + if (callbackCalled) { + ok(false, "Callback should not be invoked more than once."); + } + callbackCalled = true; + is(type, "x-html-obj-elem6", "Type is passed to the callback."); + customElements.define("x-html-obj-elem6", XObjElement5); + }); + ok(!callbackCalled, "Callback should not be called."); + let p = document.createElement("p"); + p.innerHTML = + "<x-html-obj-elem6></x-html-obj-elem6><x-html-obj-elem6></x-html-obj-elem6>"; + let el1 = p.firstChild; + let el2 = p.lastChild; + ok(callbackCalled, "Callback should be called."); + is(Object.getPrototypeOf(el1), XObjElement5.prototype, "Created element should have the prototype of the custom type."); + is(Object.getPrototypeOf(el2), XObjElement5.prototype, "Created element should have the prototype of the custom type."); +} + +function startTest() { + simpleTest(); + multipleDefinitionTest(); + throwIfDefined(); + throwIfSetTwice(); + simpleExtendedTest(); + simpleInnerHTMLTest(); + twoElementInnerHTMLTest(); +} + +startTest(); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_stack.html b/dom/tests/mochitest/webcomponents/test_custom_element_stack.html new file mode 100644 index 0000000000..1b20103eb7 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_stack.html @@ -0,0 +1,138 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=783129 +--> +<head> + <title>Test for custom elements lifecycle callback</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a> +<div id="container"> +</div> +<script> + +var container = document.getElementById("container"); + +function testChangeAttributeInEnteredViewCallback() { + var attributeChangedCallbackCalled = false; + var connectedCallbackCalled = false; + + class Two extends HTMLElement + { + connectedCallback() { + is(connectedCallbackCalled, false, "Connected callback should be called only once in this test."); + connectedCallbackCalled = true; + is(attributeChangedCallbackCalled, false, "Attribute changed callback should not be called before changing attribute."); + this.setAttribute("foo", "bar"); + is(attributeChangedCallbackCalled, true, "Transition from user-agent implementation to script should result in attribute changed callback being called."); + runNextTest(); + } + + attributeChangedCallback() { + is(connectedCallbackCalled, true, "Connected callback should have been called prior to attribute changed callback."); + is(attributeChangedCallbackCalled, false, "Attribute changed callback should only be called once in this tests."); + attributeChangedCallbackCalled = true; + } + + static get observedAttributes() { + return ["foo"]; + } + } + + customElements.define("x-two", Two); + var elem = document.createElement("x-two"); + + var container = document.getElementById("container"); + container.appendChild(elem); +} + +function testLeaveViewInEnteredViewCallback() { + var connectedCallbackCalled = false; + var disconnectedCallbackCalled = false; + var container = document.getElementById("container"); + + class Three extends HTMLElement { + connectedCallback() { + is(this.parentNode, container, "Parent node should the container in which the node was appended."); + is(connectedCallbackCalled, false, "Connected callback should be called only once in this test."); + connectedCallbackCalled = true; + is(disconnectedCallbackCalled, false, "Disconnected callback should not be called prior to removing element from document."); + container.removeChild(this); + is(disconnectedCallbackCalled, true, "Transition from user-agent implementation to script should run left view callback."); + runNextTest(); + } + + disconnectedCallback() { + is(disconnectedCallbackCalled, false, "The disconnected callback should only be called once in this test."); + is(connectedCallbackCalled, true, "The connected callback should be called prior to disconnected callback."); + disconnectedCallbackCalled = true; + } + }; + + customElements.define("x-three", Three); + var elem = document.createElement("x-three"); + + container.appendChild(elem); +} + +function testStackedAttributeChangedCallback() { + var attributeChangedCallbackCount = 0; + + var attributeSequence = ["foo", "bar", "baz"]; + + class Four extends HTMLElement + { + attributeChangedCallback(attrName, oldValue, newValue) { + if (newValue == "baz") { + return; + } + + var nextAttribute = attributeSequence.shift(); + ok(true, nextAttribute); + // Setting this attribute will call this function again, when + // control returns to the script, the last attribute in the sequence should + // be set on the element. + this.setAttribute("foo", nextAttribute); + is(this.getAttribute("foo"), "baz", "The last value in the sequence should be the value of the attribute."); + + attributeChangedCallbackCount++; + if (attributeChangedCallbackCount == 3) { + runNextTest(); + } + } + + static get observedAttributes() { + return ["foo"]; + } + } + + customElements.define("x-four", Four); + var elem = document.createElement("x-four"); + elem.setAttribute("foo", "changeme"); +} + +var testFunctions = [ + testChangeAttributeInEnteredViewCallback, + testLeaveViewInEnteredViewCallback, + testStackedAttributeChangedCallback, + SimpleTest.finish +]; + +function runNextTest() { + if (testFunctions.length) { + var nextTestFunction = testFunctions.shift(); + info(`Start ${nextTestFunction.name} ...`); + nextTestFunction(); + } +} + +SimpleTest.waitForExplicitFinish(); + +runNextTest(); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_template.html b/dom/tests/mochitest/webcomponents/test_custom_element_template.html new file mode 100644 index 0000000000..f077f84ebe --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_template.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1091425 +--> +<head> + <title>Test for custom elements in template</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<template> + <x-foo></x-foo> +</template> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1091425">Bug 1091425</a> +<script> + +class XFoo extends HTMLElement { + connectedCallback() { + ok(false, "Connected callback should not be called for custom elements in templates."); + } +}; + +customElements.define("x-foo", XFoo); + +ok(true, "Connected callback should not be called for custom elements in templates."); + +</script> +<template> + <x-foo></x-foo> +</template> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_throw_on_dynamic_markup_insertion.html b/dom/tests/mochitest/webcomponents/test_custom_element_throw_on_dynamic_markup_insertion.html new file mode 100644 index 0000000000..6c8c5e032c --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_throw_on_dynamic_markup_insertion.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1378079 +--> +<head> + <title>Test throw on dynamic markup insertion when creating element synchronously from parser</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1378079">Bug 1378079</a> +<div id="container"></div> + +<script> + +class DoDocumentOpenInCtor extends HTMLElement { + constructor() { + super(); + document.open(); + } +}; +customElements.define('x-document-open-in-ctor', DoDocumentOpenInCtor); + +class DoDocumentWriteInCtor extends HTMLElement { + constructor() { + super(); + document.write('<div>This should not be shown</div>'); + } +}; +customElements.define('x-document-write-in-ctor', DoDocumentWriteInCtor); + +class DoDocumentCloseInCtor extends HTMLElement { + constructor() { + super(); + document.close(); + } +}; +customElements.define('x-document-close-in-ctor', DoDocumentCloseInCtor); + +window.errors = []; +window.onerror = function(message, url, lineNumber, columnNumber, error) { + errors.push(error.name); + return true; +} +var expectedErrorCount = 0; + +document.write("<x-document-open-in-ctor></x-document-open-in-ctor>"); +expectedErrorCount++; +is(window.errors.length, expectedErrorCount, "expectedErrorCount should be " + expectedErrorCount); +is(window.errors[expectedErrorCount - 1], 'InvalidStateError', "Error name should be 'InvalidStateError'"); + +document.write("<x-document-write-in-ctor></x-document-write-in-ctor>"); +expectedErrorCount++; +is(window.errors.length, expectedErrorCount, "expectedErrorCount should be " + expectedErrorCount); +is(window.errors[expectedErrorCount - 1], 'InvalidStateError', "Error name should be 'InvalidStateError'"); + +document.write("<x-document-close-in-ctor></x-document-close-in-ctor>"); +expectedErrorCount++; +is(window.errors.length, expectedErrorCount, "expectedErrorCount should be " + expectedErrorCount); +is(window.errors[expectedErrorCount - 1], 'InvalidStateError', "Error name should be 'InvalidStateError'"); + +</script> + +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_uncatchable_exception.html b/dom/tests/mochitest/webcomponents/test_custom_element_uncatchable_exception.html new file mode 100644 index 0000000000..96c2add0dd --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_uncatchable_exception.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1407669 +--> +<head> + <title>Test custom elements runtime exception</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1407669">Bug 1407669</a> +<script type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() { + window.onerror = function (e) { + ok(false, "How did we get here!?"); + } + + class Foo extends HTMLElement { + constructor() { + super() + TestFunctions.throwUncatchableException(); + } + } + + customElements.define("test-custom-element", Foo); + let element = document.createElement("test-custom-element"); + is(element instanceof HTMLUnknownElement, true, "It should be a HTMLUnknownElement when uncatchable exception throws in constructor"); + ok(true, "Uncatchable exception should not report"); + SimpleTest.finish(); +}); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_upgrade.html b/dom/tests/mochitest/webcomponents/test_custom_element_upgrade.html new file mode 100644 index 0000000000..5759925cfa --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_upgrade.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1299363 +--> +<head> + <title>Test upgrade steps for custom elements.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1299363">Bug 1299363</a> +<script type="text/javascript"> +// Fake the Cu.waiveXrays, so that we can share the tests with mochitest chrome. +var Cu = { waiveXrays: (obj) => obj }; +SimpleTest.waitForExplicitFinish(); + +var promises = []; +function test_with_new_window(f, msg) { + promises.push(new Promise((aResolve) => { + let iframe = document.createElement('iframe'); + iframe.setAttribute('type', 'content'); + iframe.setAttribute('src', 'test_upgrade_page.html'); + iframe.onload = function() { + // Use window from iframe to isolate the test. + f(iframe.contentWindow, msg); + aResolve(); + }; + document.body.appendChild(iframe); + })); +} +</script> +<!-- Test cases for autonomous element --> +<script type="text/javascript" src="upgrade_tests.js"></script> +<script> +Promise.all(promises).then(() => { + SimpleTest.finish(); +}); +</script> +</body> +</html>
\ No newline at end of file diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_upgrade_chrome.html b/dom/tests/mochitest/webcomponents/test_custom_element_upgrade_chrome.html new file mode 100644 index 0000000000..5913fc8601 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_upgrade_chrome.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1299363 +--> +<head> + <title>Test upgrade steps for custom elements.</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1299363">Bug 1299363</a> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +var promises = []; +function test_with_new_window(f, msg) { + promises.push(new Promise((aResolve) => { + let iframe = document.createElement('iframe'); + iframe.setAttribute('type', 'content'); + iframe.setAttribute('src', 'http://example.org/chrome/dom/tests/mochitest/webcomponents/test_upgrade_page.html'); + iframe.onload = function() { + try { + // Use window from iframe to isolate the test. + f(iframe.contentWindow, msg); + } catch (e) { + SimpleTest.ok(false, e); + } + aResolve(); + }; + document.body.appendChild(iframe); + })); +} +</script> +<!-- Test cases for autonomous element --> +<script type="text/javascript" src="upgrade_tests.js"></script> +<script> +Promise.all(promises).then(() => { + SimpleTest.finish(); +}); +</script> +</body> +</html>
\ No newline at end of file diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_when_defined.html b/dom/tests/mochitest/webcomponents/test_custom_element_when_defined.html new file mode 100644 index 0000000000..04cca31581 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_custom_element_when_defined.html @@ -0,0 +1,140 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1275839 +--> +<head> + <title>Test custom elements whenDefined function.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1275839">Bug 1275839</a> +<iframe id="iframe"></iframe> +<script> + +SimpleTest.waitForExplicitFinish(); + +const testWindow = iframe.contentDocument.defaultView; +const customElements = testWindow.customElements; +const expectSyntaxError = 'SyntaxError'; + +function testCustomElementsAvailable() { + ok('customElements' in testWindow, '"window.customElements" exists'); + ok('define' in customElements, '"window.customElements.define" exists'); + ok('whenDefined' in customElements, '"window.customElements.get" exists'); +} + +function testCustomElementsPromiseEqually() { + // 4. If map does not contain an entry with key name, create an entry in + // map with key name and whose value is a new promise. + let promiseElement1 = customElements.whenDefined('x-element1'); + let promiseElement2 = customElements.whenDefined('x-element2'); + + ok(promiseElement1 instanceof testWindow.Promise && + promiseElement2 instanceof testWindow.Promise, + "promiseElement1 and promiseElement2 should return promises."); + + // 5. Let promise be the value of the entry in map with key name. + // 6. Return promise + let sameAsPromiseElement1 = customElements.whenDefined('x-element1'); + + ok(sameAsPromiseElement1 instanceof testWindow.Promise, + "sameAsPromiseElement1 should return promise."); + is(promiseElement1, sameAsPromiseElement1, + "Same name should return same promise."); + isnot(promiseElement1, promiseElement2, + "whenDefined() returns different promises for different names."); +} + +function testCustomElementsNameDefined() { + let name = 'x-foo'; + let beforeDefinedPromise = customElements.whenDefined(name); + + customElements.define(name, class {}); + + // 2. If this CustomElementRegistry contains an entry with name name, + // then return a new promise resolved with undefined and abort these + // steps. + return beforeDefinedPromise.then(() => { + let afterDefinedPromise = customElements.whenDefined(name); + isnot(beforeDefinedPromise, afterDefinedPromise, + "When name is defined, we should have a new promise."); + + let newPromise = customElements.whenDefined(name); + isnot(afterDefinedPromise, newPromise, + "Once name is defined, whenDefined() always returns a new promise."); + return Promise.all([newPromise, afterDefinedPromise]); + }); +} + +function testCustomElementsNameNotDefined() { + let isResolved = false; + customElements.whenDefined('x-name-not-defined').then(() => { + isResolved = true; + }); + + return new Promise((aResolve, aReject) => { + setTimeout( + function() { + ok(!isResolved, "Promise for not defined name should not be resolved."); + aResolve(); + }, 0); + }); +} + +function testCustomElementsInvalidName() { + let invalidCustomElementNames = [ + undefined, + null, + '', + '-', + 'a', + 'input', + 'mycustomelement', + 'A', + 'A-', + '0-', + 'a-A', + 'a-Z', + 'A-a', + 'a-a\u00D7', + 'a-a\u3000', + 'a-a\uDB80\uDC00', // Surrogate pair U+F0000 + // name must not be any of the hyphen-containing element names. + 'annotation-xml', + 'color-profile', + 'font-face', + 'font-face-src', + 'font-face-uri', + 'font-face-format', + 'font-face-name', + 'missing-glyph', + ]; + + let promises = []; + invalidCustomElementNames.forEach(name => { + const expectSyntaxErrorPromise = customElements.whenDefined(name); + + promises.push(expectSyntaxErrorPromise.then(() => { + ok(false, "CustomElements with invalid name should throw SyntaxError."); + }, (ex) => { + is(ex.name, expectSyntaxError, + "CustomElements with invalid name should throw SyntaxError."); + })); + }); + + return Promise.all(promises); +} + +Promise.resolve() + .then(() => testCustomElementsAvailable()) + .then(() => testCustomElementsPromiseEqually()) + .then(() => testCustomElementsNameDefined()) + .then(() => testCustomElementsNameNotDefined()) + .then(() => testCustomElementsInvalidName()) + .then(() => SimpleTest.finish()); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_detached_style.html b/dom/tests/mochitest/webcomponents/test_detached_style.html new file mode 100644 index 0000000000..4fc32d6650 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_detached_style.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1062578 +--> +<head> + <title>Test for creating style in shadow root of host not in document.</title> + <script type="text/javascript" src="head.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1062578">Bug 1062578</a> +<script> + +SimpleTest.waitForExplicitFinish(); + +var content = '<div id="grabme"></div>'; +createIframe(content) + .then((aDocument) => { + var host = aDocument.createElement("div"); + var shadow = host.attachShadow({mode: "open"}); + shadow.innerHTML = '<style> #inner { height: 200px; } </style><div id="inner">Hello</div>'; + + var iframeWin = aDocument.defaultView; + iframeWin.grabme.appendChild(host); + + var inner = shadow.getElementById("inner"); + is(iframeWin.getComputedStyle(inner).getPropertyValue("height"), "200px", "Style in shadow root should take effect."); + + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_document_adoptnode.html b/dom/tests/mochitest/webcomponents/test_document_adoptnode.html new file mode 100644 index 0000000000..50f35a55df --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_document_adoptnode.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1177991 +--> +<head> + <title>Test for Bug 1177991</title> + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1177991">Mozilla Bug 1177991</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +createIframe() + .then((aDocument) => { + var thrownException = false; + var shadowRoot = aDocument.createElement('div').attachShadow({mode: "open"}); + + try { + aDocument.adoptNode(shadowRoot); + } catch(err) { + thrownException = err; + } + + ok(thrownException !== false, "A HierarchyRequestError"); + is(thrownException.name, "HierarchyRequestError", "A HierarchyRequestError should've been thrown"); + + SimpleTest.finish(); + }); +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_document_importnode.html b/dom/tests/mochitest/webcomponents/test_document_importnode.html new file mode 100644 index 0000000000..e56fcacf27 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_document_importnode.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1177914 +--> +<head> + <title>Test for Bug 1177914</title> + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1177914">Mozilla Bug 1177914</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +createIframe() + .then((aDocument) => { + var thrownException = false; + var shadowRoot = aDocument.createElement('div').attachShadow({mode: "open"}); + + try { + aDocument.importNode(shadowRoot); + } catch(err) { + thrownException = err; + } + + ok(thrownException !== false, "A HierarchyRequestError"); + is(thrownException.name, "NotSupportedError", "A NotSupportedError exception should've been thrown"); + + SimpleTest.finish(); + }); +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_event_composed.html b/dom/tests/mochitest/webcomponents/test_event_composed.html new file mode 100644 index 0000000000..b96cd5f9dc --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_event_composed.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for composed event in web components</title> + <script type="text/javascript" src="head.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<div id="host"> +<script> + +let host = document.getElementById("host"); +let root = host.attachShadow({ mode: "open" }); + +function waitForEvent(element, eventName) { + let promises = []; + promises.push(new Promise((resolve) => { + element.addEventListener(eventName, (event) => { + is(event.composed, true, "check composed"); + resolve(); + }, { once: true }); + })); + promises.push(new Promise((resolve) => { + root.addEventListener(eventName, (event) => { + is(event.composed, true, "check composed"); + resolve(); + }, { once: true }); + })); + promises.push(new Promise((resolve) => { + host.addEventListener(eventName, (event) => { + is(event.composed, true, "check composed"); + resolve(); + }, { once: true }); + })); + return Promise.all(promises); +} + +// https://bugzilla.mozilla.org/show_bug.cgi?id=1554965 +add_task(async function input_type_date() { + let date = document.createElement("input"); + date.type = "date"; + date.value = "2020-01-01"; + root.appendChild(date); + + let promise = waitForEvent(date, "input"); + date.focus(); + synthesizeKey("KEY_ArrowDown"); + + await promise; + + date.remove(); +}); + +// https://bugzilla.mozilla.org/show_bug.cgi?id=1554965 +add_task(function input_type_time() { + let time = document.createElement("input"); + time.type = "time"; + time.value = "10:30"; + root.appendChild(time); + + let promise = waitForEvent(time, "input"); + time.focus(); + synthesizeKey("KEY_ArrowDown"); + + time.remove(); +}); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_event_retarget.html b/dom/tests/mochitest/webcomponents/test_event_retarget.html new file mode 100644 index 0000000000..05b4fa3ebe --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_event_retarget.html @@ -0,0 +1,153 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=887541 +--> +<head> + <title>Test for event retargeting in web components</title> + <script type="text/javascript" src="head.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887541">Bug 887541</a> +<script> + +SimpleTest.waitForExplicitFinish(); +createIframe() + .then((aDocument) => { + /* + * Creates an event listener with an expected event target. + */ + function createEventListener(expectedTarget, msg) { + return function(e) { + is(e.target, expectedTarget, msg); + }; + } + + /* + * Test of event retargeting through a basic ShadowRoot with a content insertion point. + * + * <div elemThree> ---- <shadow-root shadowOne> + * | | + * <div elemOne> <content elemTwo> + * + * Dispatch event on elemOne + */ + + var elemOne = aDocument.createElement("div"); + var elemTwo = aDocument.createElement("content"); + var elemThree = aDocument.createElement("div"); + var shadowOne = elemThree.attachShadow({mode: "open"}); + + elemThree.appendChild(elemOne); + shadowOne.appendChild(elemTwo); + + elemOne.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of elemOne.")); + elemTwo.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of elemTwo.")); + elemThree.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of elemThree.")); + shadowOne.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of shadowOne.")); + + var customEvent = new CustomEvent("custom", { "bubbles" : true }); + elemOne.dispatchEvent(customEvent); + + /* + * Test of event retargeting through a basic ShadowRoot with a content insertion point. + * + * <div elemThree> ---- <shadow-root shadowOne> + * | | + * <div elemOne> <content elemTwo> + * + * Dispatch event on elemTwo + */ + + elemOne = aDocument.createElement("div"); + elemTwo = aDocument.createElement("content"); + elemThree = aDocument.createElement("div"); + shadowOne = elemThree.attachShadow({mode: "open"}); + + elemThree.appendChild(elemOne); + shadowOne.appendChild(elemTwo); + + elemTwo.addEventListener("custom", createEventListener(elemTwo, "elemTwo is in common ancestor tree of elemTwo.")); + elemThree.addEventListener("custom", createEventListener(elemThree, "elemThree is in common ancestor tree of elemThree.")); + shadowOne.addEventListener("custom", createEventListener(elemTwo, "elemTwo is in common ancestor tree of shadowOne.")); + + customEvent = new CustomEvent("custom", { "bubbles" : true }); + elemTwo.dispatchEvent(customEvent); + + /* + * Test of event retargeting through a nested ShadowRoots with content insertion points. + * + * <div elemFive> --- <shadow-root shadowTwo> + * | | + * <div elemOne> <div elemFour> ----- <shadow-root shadowOne> + * | | + * <content elemTwo> <content elemThree> + * + * Dispatch custom event on elemOne. + */ + + elemOne = aDocument.createElement("div"); + elemTwo = aDocument.createElement("content"); + elemThree = aDocument.createElement("content"); + var elemFour = aDocument.createElement("div"); + var elemFive = aDocument.createElement("div"); + var shadowTwo = elemFive.attachShadow({mode: "open"}); + shadowOne = elemFour.attachShadow({mode: "open"}); + + elemFive.appendChild(elemOne); + shadowTwo.appendChild(elemFour); + elemFour.appendChild(elemTwo); + shadowOne.appendChild(elemThree); + + elemOne.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of elemOne.")); + elemTwo.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of elemTwo.")); + elemThree.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of elemThree.")); + elemFour.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of elemFour.")); + elemFive.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of elemFive.")); + shadowOne.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of shadowOne.")); + shadowTwo.addEventListener("custom", createEventListener(elemOne, "elemOne is in common ancestor tree of shadowTwo.")); + + customEvent = new CustomEvent("custom", { "bubbles" : true }); + elemOne.dispatchEvent(customEvent); + + /* + * Test of event retargeting through a nested ShadowRoots with content insertion points. + * + * <div elemFive> --- <shadow-root shadowTwo> + * | | + * <div elemOne> <div elemFour> ----- <shadow-root shadowOne> + * | | + * <content elemTwo> <content elemThree> + * + * Dispatch custom event on elemThree. + */ + + elemOne = aDocument.createElement("div"); + elemTwo = aDocument.createElement("content"); + elemThree = aDocument.createElement("content"); + elemFour = aDocument.createElement("div"); + elemFive = aDocument.createElement("div"); + shadowTwo = elemFive.attachShadow({mode: "open"}); + shadowOne = elemFour.attachShadow({mode: "open"}); + + elemFive.appendChild(elemOne); + shadowTwo.appendChild(elemFour); + elemFour.appendChild(elemTwo); + shadowOne.appendChild(elemThree); + + elemThree.addEventListener("custom", createEventListener(elemThree, "elemThree is in common ancestor tree of elemThree.")); + elemFour.addEventListener("custom", createEventListener(elemFour, "elemFour is in common ancestor tree of elemFour.")); + elemFive.addEventListener("custom", createEventListener(elemFive, "elemFive is in common ancestor tree of elemFive.")); + shadowOne.addEventListener("custom", createEventListener(elemThree, "elemThree is in common ancestor tree of shadowOne.")); + shadowTwo.addEventListener("custom", createEventListener(elemFour, "elemFour is in common ancestor tree of shadowTwo.")); + + customEvent = new CustomEvent("custom", { "bubbles" : true }); + elemThree.dispatchEvent(customEvent); + + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_event_stopping.html b/dom/tests/mochitest/webcomponents/test_event_stopping.html new file mode 100644 index 0000000000..c90988d869 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_event_stopping.html @@ -0,0 +1,174 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=887541 +--> +<head> + <title>Test for event model in web components</title> + <script type="text/javascript" src="head.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887541">Bug 887541</a> +<script> + +var els = SpecialPowers.Cc["@mozilla.org/eventlistenerservice;1"] + .getService(SpecialPowers.Ci.nsIEventListenerService); + +SimpleTest.waitForExplicitFinish(); +createIframe() + .then((aDocument) => { + function eventListener(e) { + eventChain.push(this); + } + + function isEventChain(actual, expected, msg) { + is(actual.length, expected.length, msg); + for (var i = 0; i < expected.length; i++) { + is(actual[i], expected[i], msg + " at " + i); + } + + if (actual.length) { + var chain = els.getEventTargetChainFor(actual[0], false); // Events should be dispatched on actual[0]. + ok(expected.length < chain.length, "There should be additional chrome event targets."); + } + } + + /* + * <div elemOne> ------ <shadow-root shadowOne> + * | + * <span elemTwo> + * | + * <span elemThree> + */ + + var elemOne = aDocument.createElement("div"); + var elemTwo = aDocument.createElement("span"); + var elemThree = aDocument.createElement("span"); + var shadowOne = elemOne.attachShadow({mode: "open"}); + + shadowOne.appendChild(elemTwo); + elemTwo.appendChild(elemThree); + + // Test stopping "abort" event. + + elemOne.addEventListener("abort", eventListener); + elemTwo.addEventListener("abort", eventListener); + elemThree.addEventListener("abort", eventListener); + shadowOne.addEventListener("abort", eventListener); + + var eventChain = []; + + var customEvent = new CustomEvent("abort", { "bubbles" : true }); + elemThree.dispatchEvent(customEvent); + isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that abort event is stopped at shadow root."); + + // Test stopping "error" event. + + elemOne.addEventListener("error", eventListener); + elemTwo.addEventListener("error", eventListener); + elemThree.addEventListener("error", eventListener); + shadowOne.addEventListener("error", eventListener); + + eventChain = []; + + customEvent = new CustomEvent("error", { "bubbles" : true }); + elemThree.dispatchEvent(customEvent); + isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that error event is stopped at shadow root."); + + // Test stopping "select" event. + + elemOne.addEventListener("select", eventListener); + elemTwo.addEventListener("select", eventListener); + elemThree.addEventListener("select", eventListener); + shadowOne.addEventListener("select", eventListener); + + eventChain = []; + + customEvent = new CustomEvent("select", { "bubbles" : true }); + elemThree.dispatchEvent(customEvent); + isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that select event is stopped at shadow root."); + + // Test stopping "change" event. + + elemOne.addEventListener("change", eventListener); + elemTwo.addEventListener("change", eventListener); + elemThree.addEventListener("change", eventListener); + shadowOne.addEventListener("change", eventListener); + + eventChain = []; + + customEvent = new CustomEvent("change", { "bubbles" : true }); + elemThree.dispatchEvent(customEvent); + + // Test stopping "reset" event. + + elemOne.addEventListener("reset", eventListener); + elemTwo.addEventListener("reset", eventListener); + elemThree.addEventListener("reset", eventListener); + shadowOne.addEventListener("reset", eventListener); + + eventChain = []; + + customEvent = new CustomEvent("reset", { "bubbles" : true }); + elemThree.dispatchEvent(customEvent); + isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that reset event is stopped at shadow root."); + + // Test stopping "load" event. + + elemOne.addEventListener("load", eventListener); + elemTwo.addEventListener("load", eventListener); + elemThree.addEventListener("load", eventListener); + shadowOne.addEventListener("load", eventListener); + + eventChain = []; + + customEvent = new CustomEvent("load", { "bubbles" : true }); + elemThree.dispatchEvent(customEvent); + isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that load event is stopped at shadow root."); + + // Test stopping "resize" event. + + elemOne.addEventListener("resize", eventListener); + elemTwo.addEventListener("resize", eventListener); + elemThree.addEventListener("resize", eventListener); + shadowOne.addEventListener("resize", eventListener); + + eventChain = []; + + customEvent = new CustomEvent("resize", { "bubbles" : true }); + elemThree.dispatchEvent(customEvent); + isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that resize event is stopped at shadow root."); + + // Test stopping "scroll" event. + + elemOne.addEventListener("scroll", eventListener); + elemTwo.addEventListener("scroll", eventListener); + elemThree.addEventListener("scroll", eventListener); + shadowOne.addEventListener("scroll", eventListener); + + eventChain = []; + + customEvent = new CustomEvent("scroll", { "bubbles" : true }); + elemThree.dispatchEvent(customEvent); + isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that scroll event is stopped at shadow root."); + + // Test stopping "selectstart" event. + + elemOne.addEventListener("selectstart", eventListener); + elemTwo.addEventListener("selectstart", eventListener); + elemThree.addEventListener("selectstart", eventListener); + shadowOne.addEventListener("selectstart", eventListener); + + eventChain = []; + + customEvent = new CustomEvent("selectstart", { "bubbles" : true }); + elemThree.dispatchEvent(customEvent); + isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that selectstart event is stopped at shadow root."); + + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_link_prefetch.html b/dom/tests/mochitest/webcomponents/test_link_prefetch.html new file mode 100644 index 0000000000..c376ec1981 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_link_prefetch.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=580313 +--> +<head> + <title>Test Prefetch (bug 580313)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=580313">Mozilla Bug 580313</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + + SimpleTest.waitForExplicitFinish(); + + var prefetch = SpecialPowers.Cc["@mozilla.org/prefetch-service;1"]. + getService(SpecialPowers.Ci.nsIPrefetchService); + var ios = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]. + getService(SpecialPowers.Ci.nsIIOService); + + is(prefetch.hasMoreElements(), false, "No prefetches at the test start."); + + var linkElem = document.createElement('link'); + linkElem.rel = "prefetch"; + + // Href is empty. + document.head.appendChild(linkElem); + is(prefetch.hasMoreElements(), false, + "If href is not a valid uri, a prefetch has not been started."); + + // Change uri of an existing link. Now it is a valid uri and + // a prefetch should start. + linkElem.href = "https://example.com/1"; + is(prefetch.hasMoreElements(), true, + "Setting the href to a valid uri has started a new prefetch."); + + // Removing a link, removes its prefetch. + document.head.removeChild(linkElem); + is(prefetch.hasMoreElements(), false, + "Removing the link has canceled the prefetch."); + + // Add link again. + document.head.appendChild(linkElem); + is(prefetch.hasMoreElements(), true, + "Adding link again, has started the prefetch again."); + + // Changing the href should cancel the current prefetch. + linkElem.href = "https://example.com/2"; + is(prefetch.hasMoreElements(), true, + "Changing href, a new prefetch has been started."); + // To check if "https://example.com/1" prefetch has been canceled, we try to + // cancel it using PrefetService. Since the prefetch for + // "https://example.com/1" does not exist, the cancel will throw. + var cancelError = 0; + try { + var uri = ios.newURI("https://example.com/1"); + prefetch.cancelPrefetchPreloadURI(uri, linkElem); + } catch(e) { + cancelError = 1; + } + is(cancelError, 1, "This prefetch has aleady been canceled"); + + // Now cancel the right uri. + cancelError = 0; + try { + var uri = ios.newURI("https://example.com/2"); + prefetch.cancelPrefetchPreloadURI(uri, linkElem); + } catch(e) { + cancelError = 1; + } + is(cancelError, 0, "This prefetch has been canceled successfully"); + + is(prefetch.hasMoreElements(), false, "The prefetch has already been canceled."); + + // Removing the link will do nothing regarding prefetch service. + document.head.removeChild(linkElem); + + // Adding two links to the same uri and removing one will not remove the other. + document.head.appendChild(linkElem); + is(prefetch.hasMoreElements(), true, + "Added one prefetch for 'https://example.com/2'."); + + var linkElem2 = document.createElement('link'); + linkElem2.rel = "prefetch"; + linkElem2.href = "https://example.com/2"; + document.head.appendChild(linkElem2); + is(prefetch.hasMoreElements(), true, + "Added second prefetch for 'https://example.com/2'."); + + // Remove first link element. This should not remove the prefetch. + document.head.removeChild(linkElem); + is(prefetch.hasMoreElements(), true, + "The prefetch for 'https://example.com/2' is still present."); + + // Remove the second link element. This should remove the prefetch. + document.head.removeChild(linkElem2); + is(prefetch.hasMoreElements(), false, + "There is no prefetch."); + + SimpleTest.finish(); +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_shadowdom_active_pseudo_class.html b/dom/tests/mochitest/webcomponents/test_shadowdom_active_pseudo_class.html new file mode 100644 index 0000000000..88f69190eb --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_shadowdom_active_pseudo_class.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1782142 +--> +<title>Test :active pseudo-class in shadow DOM</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/EventUtils.js"></script> +<script src="/tests/SimpleTest/paint_listener.js"></script> +<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1062578">Bug 1782142</a> +<div id="host"></div> +<script> +let { ContentTaskUtils } = SpecialPowers.ChromeUtils.import( + "resource://testing-common/ContentTaskUtils.jsm" +); + +// Setup shadow DOM +const host = document.querySelector("#host"); +const sr = host.attachShadow({mode: "closed"}); +const button = document.createElement("button"); +button.innerText = "button"; +sr.appendChild(button); + +add_task(async function init() { + await SpecialPowers.pushPrefEnv({set: [["test.events.async.enabled", true]]}); + await waitUntilApzStable(); +}); + +add_task(async function mouse_input() { + ok(!button.matches(":active"), "Button should not be active initially"); + + synthesizeMouseAtCenter(button, { type: "mousedown" }); + await ContentTaskUtils.waitForCondition(() => { + return button.matches(":active"); + }, "Button should be active"); + + synthesizeMouseAtCenter(button, { type: "mouseup" }); + await ContentTaskUtils.waitForCondition(() => { + return !button.matches(":active"); + }, "Button should not be active"); +}); + +add_task(async function touch_input() { + // To avoid contextmenu showing up during test. + document.addEventListener("contextmenu", (e) => { + e.preventDefault(); + }, { once: true }); + + ok(!button.matches(":active"), "Button should not be active initially"); + + synthesizeTouchAtCenter(button, { type: "touchstart" }); + await ContentTaskUtils.waitForCondition(() => { + return button.matches(":active"); + }, "Button should be active"); + + synthesizeTouchAtCenter(button, { type: "touchend" }); + await ContentTaskUtils.waitForCondition(() => { + return !button.matches(":active"); + }, "Button should not be active"); +}); +</script> diff --git a/dom/tests/mochitest/webcomponents/test_shadowdom_ime.html b/dom/tests/mochitest/webcomponents/test_shadowdom_ime.html new file mode 100644 index 0000000000..e023ff04a2 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_shadowdom_ime.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1553852 +--> +<head> + <title>Test for Bug 1429982</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1553852">Mozilla Bug 1553852</a> +<p id="display"></p> +<div id="content"> + <div id="host"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 1553852 **/ +SimpleTest.waitForExplicitFinish() +SimpleTest.waitForFocus(function() { + let div = document.createElement('div'); + div.setAttribute('contenteditable', 'true'); + + let shadow = document.getElementById('host').attachShadow({mode: 'open'}); + shadow.appendChild(div); + div.focus(); + + let testString = '\uD842\uDFB7\u91CE\u5BB6'; + synthesizeCompositionChange( + { "composition": + { "string": testString, + "clauses": + [ + { "length": testString.length, "attr": COMPOSITION_ATTR_RAW_CLAUSE }, + ], + }, + "caret": { "start": testString.length, "length": 0 }, + }); + is(div.innerText, testString, + "The value of contenteditable div should be " + testString + + " during composition"); + + synthesizeComposition({ type: "compositioncommitasis" }); + is(div.innerText, testString, + "The value of contenteditable div should be " + testString + + " after compositionend"); + + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> + diff --git a/dom/tests/mochitest/webcomponents/test_shadowroot.html b/dom/tests/mochitest/webcomponents/test_shadowroot.html new file mode 100644 index 0000000000..07bc8b3fcd --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_shadowroot.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=806506 +--> +<head> + <title>Test for ShadowRoot</title> + <script type="text/javascript" src="head.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a> +<script> + +SimpleTest.waitForExplicitFinish(); + +var content = '<div id="movedtoshadow" class="testclass"></div>' + + '<svg id="svgmovedtoshadow"></svg>'; +createIframe(content) + .then((aDocument) => { + // Create ShadowRoot. + var element = aDocument.createElement("div"); + ok(!element.shadowRoot, "div element should not have a shadow root."); + var shadow = element.attachShadow({mode: "open"}); + is(element.shadowRoot, shadow, "shadowRoot property should return the same shadow root that was just created."); + + // Move an element from the document to the ShadowRoot. + var inShadowEl = aDocument.getElementById("movedtoshadow"); + var inShadowSVGEl = aDocument.getElementById("svgmovedtoshadow"); + + // Test getElementById + ok(!shadow.getElementById("movedtoshadow"), "Element not in ShadowRoot should not be accessible from ShadowRoot API."); + ok(!shadow.getElementById("svgmovedtoshadow"), "SVG element not in ShadowRoot should not be accessible from ShadowRoot API."); + shadow.appendChild(inShadowEl); + shadow.appendChild(inShadowSVGEl); + is(shadow.getElementById("movedtoshadow"), inShadowEl, "Element appended to a ShadowRoot should be accessible from ShadowRoot API."); + ok(!aDocument.getElementById("movedtoshadow"), "Element appended to a ShadowRoot should not be accessible from document."); + is(shadow.getElementById("svgmovedtoshadow"), inShadowSVGEl, "SVG element appended to a ShadowRoot should be accessible from ShadowRoot API."); + ok(!aDocument.getElementById("svgmovedtoshadow"), "SVG element appended to a ShadowRoot should not be accessible from document."); + + // Remove elements from ShadowRoot and make sure that they are no longer accessible via the ShadowRoot API. + shadow.removeChild(inShadowEl); + shadow.removeChild(inShadowSVGEl); + ok(!shadow.getElementById("movedtoshadow"), "ShadowRoot API should not be able to access elements removed from ShadowRoot."); + ok(!shadow.getElementById("svgmovedtoshadow"), "ShadowRoot API should not be able to access elements removed from ShadowRoot."); + + // Test querySelector on element in a ShadowRoot. + element = aDocument.createElement("div"); + shadow = element.attachShadow({mode: "open"}); + var parentDiv = aDocument.createElement("div"); + var childSpan = aDocument.createElement("span"); + childSpan.id = "innerdiv"; + parentDiv.appendChild(childSpan); + is(parentDiv.querySelector("#innerdiv"), childSpan, "ID query selector should work on element in ShadowRoot."); + is(parentDiv.querySelector("span"), childSpan, "Tag query selector should work on element in ShadowRoot."); + + // Test that exception is thrown when trying to create a cycle with host node. + element = aDocument.createElement("div"); + shadow = element.attachShadow({mode: "open"}); + try { + shadow.appendChild(element); + ok(false, "Excpetion should be thrown when creating a cycle with host content."); + } catch (ex) { + ok(true, "Excpetion should be thrown when creating a cycle with host content."); + } + + // Basic innerHTML tests. + shadow.innerHTML = '<span id="first"></span><div id="second"></div>'; + is(shadow.childNodes.length, 2, "There should be two children in the ShadowRoot."); + is(shadow.getElementById("second").tagName, "DIV", "Elements created by innerHTML should be accessible by ShadowRoot API."); + + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_shadowroot_clonenode.html b/dom/tests/mochitest/webcomponents/test_shadowroot_clonenode.html new file mode 100644 index 0000000000..e344615596 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_shadowroot_clonenode.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1429982 +--> +<head> + <title>Test for Bug 1429982</title> + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1429982">Mozilla Bug 1429982</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 1429982 **/ +SimpleTest.waitForExplicitFinish(); +createIframe() + .then((aDocument) => { + var element = aDocument.createElement("div"); + var shadowRoot = element.attachShadow({mode: "open"}); + var thrownException = false; + + try { + shadowRoot.cloneNode(); + } catch(err) { + thrownException = err; + } + + ok(thrownException !== false, "An exception should've been thrown"); + is(thrownException.name, "NotSupportedError", "A NotSupportedError exception should've been thrown"); + + SimpleTest.finish(); + }); +</script> +</pre> +</body> +</html> + diff --git a/dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html b/dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html new file mode 100644 index 0000000000..7fd5f78122 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=806506 +--> +<head> + <title>Test for inert elements in ShadowRoot</title> + <script type="text/javascript" src="head.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a> +<script> + +SimpleTest.waitForExplicitFinish(); + +var content = '<div id="grabme"></div>'; +createIframe(content).then(aDocument => { + var element = aDocument.getElementById("grabme"); + var shadow = element.attachShadow({mode: "open"}); + + // Check that <base> is inert. + shadow.innerHTML = '<base href="http://www.example.org/" />'; + isnot(aDocument.baseURI, "http://www.example.org/", "Base element should be inert in ShadowRoot."); + SimpleTest.finish(); +}); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_shadowroot_style.html b/dom/tests/mochitest/webcomponents/test_shadowroot_style.html new file mode 100644 index 0000000000..824a8decea --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style.html @@ -0,0 +1,85 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=806506 +--> +<head> + <title>Test for ShadowRoot styling</title> + <script type="text/javascript" src="head.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a> +<script> + +SimpleTest.waitForExplicitFinish(); + +var content = '<div class="tall" id="bodydiv"></div>' + + '<div id="container"></div>'; +createIframe(content) + .then((aDocument) => { + var iframeWin = aDocument.defaultView; + + // Create ShadowRoot. + var container = aDocument.getElementById("container"); + var elem = aDocument.createElement("div"); + container.appendChild(elem); // Put ShadowRoot host in document. + var root = elem.attachShadow({mode: "open"}); + + // A style element that will be appended into the ShadowRoot. + var shadowStyle = aDocument.createElement("style"); + shadowStyle.innerHTML = ".tall { height: 100px; } .fat { padding-left: inherit; }"; + + root.innerHTML = '<div id="divtostyle" class="tall fat"></div>'; + var divToStyle = root.getElementById("divtostyle"); + + // Make sure styleSheet counts are correct after appending a style to the ShadowRoot. + is(aDocument.styleSheets.length, 0, "There shouldn't be any style sheet in the test frame document."); + is(root.styleSheets.length, 0, "The ShadowRoot should have no style sheets."); + root.appendChild(shadowStyle); + is(aDocument.styleSheets.length, 0, "Styles in the ShadowRoot element should not be accessible from the document."); + is(root.styleSheets.length, 1, "ShadowRoot should have one style sheet from the appened style."); + is(root.styleSheets[0].ownerNode, shadowStyle, "First style in ShadowRoot should match the style that was just appended."); + + var dummyStyle = aDocument.createElement("style"); + root.appendChild(dummyStyle); + is(root.styleSheets.length, 2, "ShadowRoot should have an additional style from appending dummyStyle."); + is(root.styleSheets[1].ownerNode, dummyStyle, "Second style in ShadowRoot should be the dummyStyle."); + root.removeChild(dummyStyle); + is(root.styleSheets.length, 1, "Removing dummyStyle should remove it from the ShadowRoot style sheets."); + is(root.styleSheets[0].ownerNode, shadowStyle, "The style sheet remaining in the ShadowRoot should be shadowStyle."); + + // Make sure that elements outside of the ShadowRoot are not affected by the ShadowRoot style. + isnot(iframeWin.getComputedStyle(aDocument.getElementById("bodydiv")).getPropertyValue("height"), "100px", "Style sheets in ShadowRoot should not apply to elements no in the ShadowRoot."); + + // Make sure that elements in the ShadowRoot are styled according to the ShadowRoot style. + is(iframeWin.getComputedStyle(divToStyle).getPropertyValue("height"), "100px", "ShadowRoot style sheets should apply to elements in ShadowRoot."); + + // Tests for author styles not applying in a ShadowRoot. + var authorStyle = aDocument.createElement("style"); + authorStyle.innerHTML = ".fat { padding-right: 20px; padding-left: 30px; }"; + aDocument.body.appendChild(authorStyle); + isnot(iframeWin.getComputedStyle(divToStyle).getPropertyValue("padding-right"), "20px", "Author styles should not apply to ShadowRoot."); + + // Test dynamic changes to style in ShadowRoot. + root.innerHTML = '<div id="divtostyle" class="dummy"></div>'; + divToStyle = root.getElementById("divtostyle"); + var dummyShadowStyle = aDocument.createElement("style"); + dummyShadowStyle.innerHTML = ".dummy { height: 300px; }"; + root.appendChild(dummyShadowStyle); + is(iframeWin.getComputedStyle(divToStyle).getPropertyValue("height"), "300px", "Dummy element in ShadowRoot should be styled by style in ShadowRoot."); + dummyShadowStyle.innerHTML = ".dummy { height: 200px; }"; + is(iframeWin.getComputedStyle(divToStyle).getPropertyValue("height"), "200px", "Dynamic changes to styles in ShadowRoot should change style of affected elements."); + + // Test id selector in ShadowRoot style. + root.innerHTML = '<style>#divtostyle { padding-top: 10px; }</style><div id="divtostyle"></div>'; + divToStyle = root.getElementById("divtostyle"); + is(iframeWin.getComputedStyle(divToStyle).getPropertyValue("padding-top"), "10px", "ID selector in style selector should match element."); + + SimpleTest.finish(); + }); +</script> +</body> +</html> + diff --git a/dom/tests/mochitest/webcomponents/test_shadowroot_style_order.html b/dom/tests/mochitest/webcomponents/test_shadowroot_style_order.html new file mode 100644 index 0000000000..05976a934a --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style_order.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=806506 +--> +<head> + <title>Test for ShadowRoot style order</title> + <script type="text/javascript" src="head.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a> +<script> + +SimpleTest.waitForExplicitFinish(); + +var content = '<div id="container"></div>'; +createIframe(content) + .then((aDocument) => { + var iframeWin = aDocument.defaultView; + + // Create ShadowRoot. + var container = aDocument.getElementById("container"); + var elem = aDocument.createElement("div"); + container.appendChild(elem); // Put ShadowRoot host in document. + var root = elem.attachShadow({mode: "open"}); + + // Style elements that will be appended into the ShadowRoot. + var tallShadowStyle = aDocument.createElement("style"); + tallShadowStyle.innerHTML = ".tall { height: 100px; }"; + + var veryTallShadowStyle = aDocument.createElement("style"); + veryTallShadowStyle.innerHTML = ".tall { height: 200px; }"; + + var divToStyle = aDocument.createElement("div"); + divToStyle.setAttribute("class", "tall"); + root.appendChild(divToStyle); + + // Make sure the styles are applied in tree order. + root.appendChild(tallShadowStyle); + is(root.styleSheets.length, 1, "ShadowRoot should have one style sheet."); + is(iframeWin.getComputedStyle(divToStyle).getPropertyValue("height"), "100px", "Style in ShadowRoot should apply to elements in ShadowRoot."); + root.appendChild(veryTallShadowStyle); + is(root.styleSheets.length, 2, "ShadowRoot should have two style sheets."); + is(iframeWin.getComputedStyle(divToStyle).getPropertyValue("height"), "200px", "Style in ShadowRoot should apply to elements in ShadowRoot in tree order."); + + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_style_fallback_content.html b/dom/tests/mochitest/webcomponents/test_style_fallback_content.html new file mode 100644 index 0000000000..33aef21322 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_style_fallback_content.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=806506 +--> +<head> + <title>Test for styling fallback content</title> + <script type="text/javascript" src="head.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a> +<script> + +SimpleTest.waitForExplicitFinish(); + +var content = '<div id="grabme"></div>'; +createIframe(content) + .then((aDocument) => { + var iframeWin = aDocument.defaultView; + + var host = aDocument.getElementById("grabme"); + var shadow = host.attachShadow({mode: "open"}); + shadow.innerHTML = '<style id="innerstyle"></style><span id="container"><slot><span id="innerspan">Hello</span></slot></span>'; + var innerStyle = shadow.getElementById("innerstyle"); + + innerStyle.innerHTML = '#innerspan { margin-top: 10px; }'; + var innerSpan = shadow.getElementById("innerspan"); + is(iframeWin.getComputedStyle(innerSpan).getPropertyValue("margin-top"), "10px", "Default content should be style by id selector."); + + innerStyle.innerHTML = '#container > slot > #innerspan { margin-top: 30px; }'; + is(iframeWin.getComputedStyle(innerSpan).getPropertyValue("margin-top"), "30px", "Default content should be style by child combinators."); + + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_template.html b/dom/tests/mochitest/webcomponents/test_template.html new file mode 100644 index 0000000000..8e663b2c93 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_template.html @@ -0,0 +1,153 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=818976 +--> +<head> + <title>Test for template element</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script> + function shouldNotCall() { + ok(false, "Template contents should be inert."); + } + </script> + <template> + <script> + shouldNotCall(); + </script> + </template> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=818976">Bug 818976</a> +<template id="grabme"><div id="insidetemplate"></div></template> +<template id="justtemplate"></template> +<template id="first">Hi<template>Bye</template></template> +<div><template id="second"><span></span></template></div> +<template id="cloneme"><span>I want a clone</span><span>me too</span></template> +<template id="cycleone"></template> +<template id="cycletwo"><template></template></template> +<template id="cyclethree"></template> +<template id="cyclefour"><template></template></template> +<template id="appendtome"></template> +<template id="insertinme"></template> +<template> + <script> + shouldNotCall(); + </script> +</template> +<div id="fillme"></div> +<script> +var templateEl = document.getElementById("grabme"); +ok(templateEl, "template element should be in document."); +is(window.getComputedStyle(templateEl).display, "none", "Template element should not be visible."); +ok(!document.getElementById("insidetemplate"), "Template content should not be in document."); +is(templateEl.childNodes.length, 0, "Template element should have no children."); +is(templateEl.content.childNodes.length, 1, "Template content should have 1 child <div>."); + +// Make sure that template is owned by different document. +ok(templateEl.content.ownerDocument != templateEl.ownerDocument, "Template should be in a different document because the current document has a browsing context."); +var otherTemplateEl = document.getElementById("first"); +is(templateEl.content.ownerDocument, otherTemplateEl.content.ownerDocument, "Template contents within the same document should be owned by the same template contents owner."); + +var htmlDoc = document.implementation.createHTMLDocument(); +var otherDocTemplateEl = htmlDoc.createElement("template"); +isnot(otherDocTemplateEl.content.ownerDocument, htmlDoc, "Template content owner should be a new document."); + +var templateOwnerDoc = otherDocTemplateEl.content.ownerDocument; +var docCreatedTemplateEl = templateOwnerDoc.createElement("template"); +is(docCreatedTemplateEl.content.ownerDocument, templateOwnerDoc, "Template content owner of template elements created by a template document should be the template document."); + +// Tests for XMLSerializer +templateEl = document.getElementById("justtemplate"); +var serializer = new XMLSerializer(); +is(serializer.serializeToString(templateEl), '<template xmlns="http://www.w3.org/1999/xhtml" id="justtemplate"></template>', "XMLSerializer should serialize template element."); + +templateEl = document.getElementById("first"); +is(serializer.serializeToString(templateEl), '<template xmlns="http://www.w3.org/1999/xhtml" id="first">Hi<template>Bye</template></template>', "XMLSerializer should serialize template content."); + +// Tests for innerHTML. +is(templateEl.innerHTML, 'Hi<template>Bye</template>', "innerHTML should serialize content."); +// Tests for outerHTML, not specified but should do something reasonable. +is(templateEl.outerHTML, '<template id="first">Hi<template>Bye</template></template>', "outerHTML should serialize content."); + +templateEl.innerHTML = "Hello"; +is(templateEl.innerHTML, "Hello", "innerHTML of template should be set to 'Hello'"); +is(templateEl.childNodes.length, 0, "Template element should have no children."); +is(templateEl.content.childNodes.length, 1, "Template content should have 'Hello' as child."); + +// Test for innerHTML on parent of template element. +var templateParent = document.getElementById("second").parentNode; +is(templateParent.innerHTML, '<template id="second"><span></span></template>', "InnerHTML on parent of template element should serialize template and template content."); + +templateEl.innerHTML = '<template id="inner">Hello</template>'; +ok(templateEl.content.childNodes[0] instanceof HTMLTemplateElement, "Template content should have <template> as child."); +is(templateEl.content.childNodes[0].childNodes.length, 0, "Parsed temlate element should have no children."); +is(templateEl.content.childNodes[0].content.childNodes.length, 1, "Parsed temlate element should have 'Hello' in content."); + +// Test cloning. +templateEl = document.getElementById("cloneme"); +var nonDeepClone = templateEl.cloneNode(false); +is(nonDeepClone.childNodes.length, 0, "There should be no children on the clone."); +is(nonDeepClone.content.childNodes.length, 0, "Content should not be cloned."); +var deepClone = templateEl.cloneNode(true); +is(deepClone.childNodes.length, 0, "There should be no children on the clone."); +is(deepClone.content.childNodes.length, 2, "The content should be cloned."); + +// Append content into a node. +var parentEl = document.getElementById("fillme"); +parentEl.appendChild(templateEl.content); +is(parentEl.childNodes.length, 2, "Parent should be appended with cloned content."); + +// Test exceptions thrown for cycles. +templateEl = document.getElementById("cycleone"); +try { + templateEl.content.appendChild(templateEl); + ok(false, "Exception should be thrown when creating cycles in template content."); +} catch (ex) { + ok(true, "Exception should be thrown when creating cycles in template content."); +} + +templateEl = document.getElementById("cycletwo"); +try { + // Append template to template content within the template content. + templateEl.content.childNodes[0].content.appendChild(templateEl); + ok(false, "Exception should be thrown when creating cycles in template content."); +} catch (ex) { + ok(true, "Exception should be thrown when creating cycles in template content."); +} + +templateEl = document.getElementById("cyclethree"); +try { + templateEl.appendChild(templateEl); + ok(false, "Exception should be thrown when creating cycles in hierarchy."); +} catch (ex) { + ok(true, "Exception should be thrown when creating cycles in hierarchy."); +} + +templateEl = document.getElementById("cyclefour"); +try { + templateEl.content.childNodes[0].appendChild(templateEl); + ok(false, "Exception should be thrown when creating cycles in hierarchy."); +} catch (ex) { + ok(true, "Exception should be thrown when creating cycles in hierarchy."); +} + +templateEl = document.getElementById("insertinme"); +var sentinel = document.createElement("div"); +try { + templateEl.content.appendChild(sentinel); + templateEl.content.insertBefore(templateEl, sentinel); + ok(false, "Exception should be thrown when creating cycles in hierarchy."); +} catch (ex) { + ok(true, "Exception should be thrown when creating cycles in hierarchy."); +} + +// Appending normal stuff into content should work. +templateEl = document.getElementById("appendtome"); +templateEl.content.appendChild(document.createElement("div")); +is(templateEl.content.childNodes.length, 1, "Template should have div element appended as child"); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_template_xhtml.html b/dom/tests/mochitest/webcomponents/test_template_xhtml.html new file mode 100644 index 0000000000..b14e5c365a --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_template_xhtml.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1011831 +--> +<head> + <title>Test for template element</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1011831">Bug 1011831</a> +<script> +var docSrc = + '<!DOCTYPE html>' + + '<html xmlns="http://www.w3.org/1999/xhtml">' + + '<body>' + + '<template id="t">Content<span>Content</span></template>' + + '<div id="container"><template>One</template><div>Two</div></div>' + + '<template id="t2"></template>' + + '</body>' + + '</html>'; + +var doc = (new DOMParser()).parseFromString(docSrc, 'application/xhtml+xml'); + +var t = doc.getElementById("t"); +is(t.childNodes.length, 0, "Template should have no children."); +is(t.content.childNodes.length, 2, "Template content should have two children, text node and a span."); + +// Test serialization of template element. +is(t.innerHTML, 'Content<span xmlns="http://www.w3.org/1999/xhtml">Content</span>', "Template contents should be serialized."); +is(t.outerHTML, '<template xmlns="http://www.w3.org/1999/xhtml" id="t">Content<span>Content</span></template>', "Template contents should be serialized."); + +var c = doc.getElementById("container"); +is(c.innerHTML, '<template xmlns="http://www.w3.org/1999/xhtml">One</template><div xmlns="http://www.w3.org/1999/xhtml">Two</div>', "Template contents should be serialized."); +is(c.outerHTML, '<div xmlns="http://www.w3.org/1999/xhtml" id="container"><template>One</template><div>Two</div></div>', "Template contents should be serialized."); + +// Test setting innerHTML on template element. +var t2 = doc.getElementById("t2"); +t2.innerHTML = 'Three<span>Four</span>'; +is(t2.childNodes.length, 0, "Setting innerHTML should append children into template content."); +is(t2.content.childNodes.length, 2, "Setting innerHTML should append children into template content."); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/webcomponents/test_upgrade_page.html b/dom/tests/mochitest/webcomponents/test_upgrade_page.html new file mode 100644 index 0000000000..d1822b715e --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_upgrade_page.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<title>Upgrade test page</title> +<meta charset="utf-8"/> +</head> +<body> +<p>Upgrade test page</p> +<unresolved-element></unresolved-element> +</body> +</html>
\ No newline at end of file diff --git a/dom/tests/mochitest/webcomponents/test_xul_custom_element.xhtml b/dom/tests/mochitest/webcomponents/test_xul_custom_element.xhtml new file mode 100644 index 0000000000..f2c50f7bde --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_xul_custom_element.xhtml @@ -0,0 +1,386 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="XUL Custom Elements" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="runTest();"> + <title>XUL Custom Elements</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script type="application/javascript"> + <![CDATA[ + SimpleTest.waitForExplicitFinish(); + + var gXULDOMParser = new DOMParser(); + gXULDOMParser.forceEnableXULXBL(); + + function parseXULToFragment(str) { + let doc = gXULDOMParser.parseFromSafeString(` + <box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">${str}</box>`, + "application/xml"); + // We use a range here so that we don't access the inner DOM elements from + // JavaScript before they are imported and inserted into a document. + let range = doc.createRange(); + range.selectNodeContents(doc.firstChild); + return range.extractContents(); + } + + class TestCustomElement extends XULElement { + constructor() { + super(); + + this.attachShadow({mode: "open"}); + } + + connectedCallback() { + this.textContent = "foo"; + } + } + + customElements.define("test-custom-element", TestCustomElement); + + class TestWithoutDash extends XULElement { } + customElements.define("testwithoutdash", TestWithoutDash); + + class TestWithoutDashExtended extends TestWithoutDash { + constructor() { + super(); + } + + connectedCallback() { + this.textContent = "quux"; + } + } + customElements.define("testwithoutdash-extended", TestWithoutDashExtended, { extends: "testwithoutdash" }); + + class TestCustomBuiltInElement extends XULElement { + constructor() { + super(); + } + + connectedCallback() { + this.textContent = "baz"; + } + } + customElements.define("test-built-in-element", + TestCustomBuiltInElement, { extends: "axulelement" }); + + class TestPopupExtendElement extends XULPopupElement { + constructor() { + super(); + } + + connectedCallback() { + this.textContent = "quuz"; + } + } + customElements.define("test-popup-extend", + TestPopupExtendElement, { extends: "popup" }); + + class TestCustomElement3 extends XULElement { } + + customElements.setElementCreationCallback( + "test-custom-element-3", () => customElements.define("test-custom-element-3", TestCustomElement3)); + + const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + + function basicCustomElementCreate() { + let element = document.createElementNS(XUL_NS, "test-custom-element"); + ok(element.shadowRoot, "Shadow DOM works even with pref off"); + document.querySelector("#content").appendChild(element); + is(element.textContent, "foo", "Should have set the textContent"); + ok(element instanceof TestCustomElement, "Should be an instance of TestCustomElement"); + + let element2 = element.cloneNode(false); + is(element2.textContent, "", "Shouldn't have cloned the textContent"); + document.querySelector("#content").appendChild(element2); + is(element2.textContent, "foo", "Should have set the textContent"); + ok(element2 instanceof TestCustomElement, "Should be an instance of TestCustomElement"); + + let element3 = new TestCustomElement(); + is(element3.localName, "test-custom-element", "Should see the right tag"); + is(element3.textContent, "", "Shouldn't have been inserted yet"); + is(element3.namespaceURI, XUL_NS, "Should have set the right namespace"); + document.querySelector("#content").appendChild(element3); + is(element3.textContent, "foo", "Should have set the textContent"); + ok(element3 instanceof TestCustomElement, "Should be an instance of TestCustomElement"); + + document.querySelector("#content").appendChild(parseXULToFragment(`<test-custom-element />`)); + let element4 = document.querySelector("#content").lastChild; + is(element4.localName, "test-custom-element", "Should see the right tag"); + is(element4.namespaceURI, XUL_NS, "Should have set the right namespace"); + is(element4.textContent, "foo", "Should have set the textContent"); + ok(element4 instanceof TestCustomElement, "Should be an instance of TestCustomElement"); + } + + function parserBasicElementUpgrade() { + let element = document.getElementById("element4"); + is(element.textContent, "foo", + "Parser should have instantiated the custom element."); + ok(element instanceof TestCustomElement, "Should be an instance of TestCustomElement"); + } + + function tagNameWithoutDash() { + let element = document.getElementById("element5"); + ok(element instanceof TestWithoutDash, "Should be an instance of TestWithoutDash"); + } + + function upgradeAfterDefine() { + class TestCustomElement1 extends XULElement { + constructor() { + super(); + } + + connectedCallback() { + this.textContent = "bar"; + } + } + + let element = document.createElementNS(XUL_NS, "test-custom-element-1"); + ok(!(element instanceof TestCustomElement1), "Should not be an instance of TestCustomElement1"); + customElements.define("test-custom-element-1", TestCustomElement1); + ok(!(element instanceof TestCustomElement1), "Should not be an instance of TestCustomElement1"); + document.querySelector("#content").appendChild(element); + ok(element instanceof TestCustomElement1, "Should be upgraded to an instance of TestCustomElement1"); + is(element.textContent, "bar", "Should have set the textContent"); + } + + function basicElementCreateBuiltIn() { + let element = document.createElementNS(XUL_NS, "axulelement", { is: "test-built-in-element" }); + ok(element instanceof TestCustomBuiltInElement, "Should be an instance of TestCustomBuiltInElement"); + is(element.getAttribute("is"), "", "The |is| attribute of the created element should not be the extended type."); + document.querySelector("#content").appendChild(element); + is(element.textContent, "baz", "Should have set the textContent"); + + let element2 = element.cloneNode(false); + is(element2.localName, "axulelement", "Should see the right tag"); + is(element2.getAttribute("is"), "", "The |is| attribute of the created element should not be the extended type."); + is(element2.textContent, "", "Shouldn't have cloned the textContent"); + document.querySelector("#content").appendChild(element2); + is(element2.textContent, "baz", "Should have set the textContent"); + ok(element2 instanceof TestCustomBuiltInElement, "Should be an instance of TestCustomBuiltInElement"); + + let element3 = new TestCustomBuiltInElement(); + is(element3.localName, "axulelement", "Should see the right tag"); + is(element3.textContent, "", "Shouldn't have been inserted yet"); + is(element3.namespaceURI, XUL_NS, "Should have set the right namespace"); + document.querySelector("#content").appendChild(element3); + is(element3.textContent, "baz", "Should have set the textContent"); + ok(element3 instanceof TestCustomBuiltInElement, "Should be an instance of TestCustomBuiltInElement"); + + document.querySelector("#content").appendChild(parseXULToFragment(`<axulelement is="test-built-in-element" />`)) + let element4 = document.querySelector("#content").lastChild; + is(element4.localName, "axulelement", "Should see the right tag"); + is(element4.namespaceURI, XUL_NS, "Should have set the right namespace"); + is(element4.textContent, "baz", "Should have set the textContent"); + ok(element4 instanceof TestCustomBuiltInElement, "Should be an instance of TestCustomBuiltInElement"); + } + + function parserBasicElementUpgradeBuiltIn() { + let element = document.getElementById("element6"); + is(element.textContent, "baz", + "Parser should have instantiated the custom element."); + ok(element instanceof TestCustomBuiltInElement, "Should be an instance of TestCustomBuiltInElement"); + } + + function subclassElementCreateBuiltIn() { + let element = document.createElementNS(XUL_NS, "popup", { is: "test-popup-extend" }); + ok(element instanceof TestPopupExtendElement, "Should be an instance of TestPopupExtendElement"); + is(element.getAttribute("is"), "", "The |is| attribute of the created element should not be the extended type."); + document.querySelector("#content").appendChild(element); + is(element.textContent, "quuz", "Should have set the textContent"); + + let element2 = element.cloneNode(false); + is(element2.localName, "popup", "Should see the right tag"); + is(element2.getAttribute("is"), "", "The |is| attribute of the created element should not be the extended type."); + is(element2.textContent, "", "Shouldn't have cloned the textContent"); + document.querySelector("#content").appendChild(element2); + is(element2.textContent, "quuz", "Should have set the textContent"); + ok(element2 instanceof TestPopupExtendElement, "Should be an instance of TestPopupExtendElement"); + + let element3 = new TestPopupExtendElement(); + is(element3.localName, "popup", "Should see the right tag"); + is(element3.textContent, "", "Shouldn't have been inserted yet"); + is(element3.namespaceURI, XUL_NS, "Should have set the right namespace"); + document.querySelector("#content").appendChild(element3); + is(element3.textContent, "quuz", "Should have set the textContent"); + ok(element3 instanceof TestPopupExtendElement, "Should be an instance of TestPopupExtendElement"); + + document.querySelector("#content").appendChild(parseXULToFragment(`<popup is="test-popup-extend" />`)) + let element4 = document.querySelector("#content").lastChild; + is(element4.localName, "popup", "Should see the right tag"); + is(element4.namespaceURI, XUL_NS, "Should have set the right namespace"); + is(element4.textContent, "quuz", "Should have set the textContent"); + ok(element4 instanceof TestPopupExtendElement, "Should be an instance of TestPopupExtendElement"); + } + + function parserSubclassElementUpgradeBuiltIn() { + let element = document.getElementById("element7"); + is(element.textContent, "quuz", + "Parser should have instantiated the custom element."); + ok(element instanceof TestPopupExtendElement, "Should be an instance of TestPopupExtendElement"); + } + + function upgradeAfterDefineBuiltIn() { + class TestCustomBuiltInElement1 extends XULElement { + constructor() { + super(); + } + + connectedCallback() { + this.textContent = "qux"; + } + } + let element = document.createElementNS(XUL_NS, "axulelement", { is: "test-built-in-element-1" }); + ok(!(element instanceof TestCustomBuiltInElement1), "Should not be an instance of TestCustomBuiltInElement1"); + customElements.define("test-built-in-element-1", + TestCustomBuiltInElement1, { extends: "axulelement" }); + ok(!(element instanceof TestCustomBuiltInElement1), "Should not be an instance of TestCustomBuiltInElement1"); + document.querySelector("#content").appendChild(element); + ok(element instanceof TestCustomBuiltInElement1, "Should be upgraded to an instance of TestCustomBuiltInElement1"); + is(element.textContent, "qux", "Should have set the textContent"); + } + + function throwForInvalidBuiltInName() { + try { + // <axulelement is="testwithoutdashbuiltin" /> is not allowed; + // built-in type names need dashes. + customElements.define( + "testwithoutdashbuiltin", class extends XULElement {}, { extends: "axulelement" }); + ok(false, "Built-in type name without dash should be rejected."); + } catch (e) { + ok(true, "Built-in type name without dash is rejected."); + } + try { + // <test-built-in-element-2 is="test-custom-element-2" /> is not allowed; + // built-in type tag names forbid dashes + customElements.define( + "test-built-in-element-2", class extends XULElement {}, { extends: "test-custom-element-2" }); + ok(false, "Extending from a name with dash should be rejected."); + } catch (e) { + ok(true, "Extending from a name with dash is rejected."); + } + } + + function extendingWithoutDashCustomElement() { + let element = document.createElementNS(XUL_NS, "testwithoutdash", { is: "testwithoutdash-extended" }); + ok(element instanceof TestWithoutDashExtended, "Should be an instance of TestWithoutDashExtended"); + ok(element instanceof TestWithoutDash, "Should be an instance of TestWithoutDash"); + is(element.getAttribute("is"), "", "The |is| attribute of the created element should not be the extended type."); + document.querySelector("#content").appendChild(element); + is(element.textContent, "quux", "Should have set the textContent"); + + let element2 = element.cloneNode(false); + is(element2.localName, "testwithoutdash", "Should see the right tag"); + is(element2.getAttribute("is"), "", "The |is| attribute of the created element should not be the extended type."); + is(element2.textContent, "", "Shouldn't have cloned the textContent"); + document.querySelector("#content").appendChild(element2); + is(element2.textContent, "quux", "Should have set the textContent"); + ok(element2 instanceof TestWithoutDashExtended, "Should be an instance of TestWithoutDashExtended"); + ok(element2 instanceof TestWithoutDash, "Should be an instance of TestWithoutDash"); + + let element3 = new TestWithoutDashExtended(); + is(element3.localName, "testwithoutdash", "Should see the right tag"); + is(element3.textContent, "", "Shouldn't have been inserted yet"); + is(element3.namespaceURI, XUL_NS, "Should have set the right namespace"); + document.querySelector("#content").appendChild(element3); + is(element3.textContent, "quux", "Should have set the textContent"); + ok(element3 instanceof TestWithoutDashExtended, "Should be an instance of TestWithoutDashExtended"); + ok(element3 instanceof TestWithoutDash, "Should be an instance of TestWithoutDash"); + + document.querySelector("#content").appendChild(parseXULToFragment(`<testwithoutdash is="testwithoutdash-extended" />`)) + let element4 = document.querySelector("#content").lastChild; + is(element4.localName, "testwithoutdash", "Should see the right tag"); + is(element4.namespaceURI, XUL_NS, "Should have set the right namespace"); + is(element4.textContent, "quux", "Should have set the textContent"); + ok(element4 instanceof TestWithoutDashExtended, "Should be an instance of TestWithoutDashExtended"); + ok(element4 instanceof TestWithoutDash, "Should be an instance of TestWithoutDash"); + } + + function nonCustomElementCreate() { + // All of these should be created as plain XUL elements without hitting + // any assertions. + let elements = [ + document.createElementNS(XUL_NS, "axulelement", { is: "test-custom-element" }), + document.createElementNS(XUL_NS, "axulelement", { is: "testwithoutdash" }), + document.createElementNS(XUL_NS, "axulelement", { is: "test-custom-element-1" }), + document.createElementNS(XUL_NS, "name-with-dash", { is: "name-with-dash" }), + document.createElementNS(XUL_NS, "name-with-dash", { is: "another-name-with-dash" }), + document.createElementNS(XUL_NS, "testwithoutdash-extended"), + document.createElementNS(XUL_NS, "test-built-in-element"), + document.createElementNS(XUL_NS, "test-popup-extend"), + document.createElementNS(XUL_NS, "test-built-in-element-1")]; + + for (let element of elements) { + is(Object.getPrototypeOf(element), XULElement.prototype, + `<${element.localName} is="${element.getAttribute("is")}" /> should not be a custom element.`); + } + } + + function testSetElementCreationballbackInDocument() { + let element = document.getElementById("element8"); + ok(element instanceof TestCustomElement3, "Should be an instance of TestCustomElement3"); + } + + function setElementCreationCallbackCreate() { + class TestCustomElement4 extends XULElement {} + customElements.setElementCreationCallback( + "test-custom-element-4", () => customElements.define("test-custom-element-4", TestCustomElement4)); + + let element = document.createElementNS(XUL_NS, "test-custom-element-4"); + ok(element instanceof TestCustomElement4, "Should be an instance of TestCustomElement4"); + + class TestCustomElement5 extends XULElement {} + customElements.setElementCreationCallback( + "test-custom-element-5", () => { + ok(true, "test-custom-element-5 callback called"); + customElements.define("test-custom-element-5", TestCustomElement5); + }); + + document.querySelector("#content").appendChild(parseXULToFragment(`<test-custom-element-5 />`)); + let element1 = document.querySelector("#content").lastChild; + ok(element1 instanceof TestCustomElement5, "Should be an instance of TestCustomElement5"); + } + + function runTest() { + basicCustomElementCreate(); + parserBasicElementUpgrade(); + + tagNameWithoutDash(); + upgradeAfterDefine(); + + basicElementCreateBuiltIn(); + parserBasicElementUpgradeBuiltIn(); + + subclassElementCreateBuiltIn(); + parserSubclassElementUpgradeBuiltIn(); + + upgradeAfterDefineBuiltIn(); + + throwForInvalidBuiltInName(); + extendingWithoutDashCustomElement(); + + nonCustomElementCreate(); + + testSetElementCreationballbackInDocument(); + setElementCreationCallbackCreate(); + + SimpleTest.finish(); + } + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"> + <test-custom-element id="element4" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/> + <testwithoutdash id="element5" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/> + <axulelement id="element6" is="test-built-in-element" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/> + <popup id="element7" is="test-popup-extend" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/> + <test-custom-element-3 id="element8" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"></test-custom-element-3> + </div> + <pre id="test"></pre> + </body> +</window> diff --git a/dom/tests/mochitest/webcomponents/test_xul_shadowdom_accesskey.xhtml b/dom/tests/mochitest/webcomponents/test_xul_shadowdom_accesskey.xhtml new file mode 100644 index 0000000000..79fb167276 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_xul_shadowdom_accesskey.xhtml @@ -0,0 +1,60 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="XUL ShadowDOM accesskey" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1037709" + title="XUL ShadowDOM accesskey"> +Mozilla Bug 1037709 +</a> +<div id="container" style="position: relative"></div> +</body> +<!-- Tests code --> +<script type="application/javascript"> +<![CDATA[ + +const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; +const container = document.getElementById("container"); + +function pressAccessKey(accessKey){ + synthesizeKey(accessKey, navigator.platform.includes("Mac") ? { altKey: true, ctrlKey: true } + : { altKey: true, shiftKey: true }); +} + +function testAccesskeyInShadowTree(mode) { + add_task(async () => { + const host = document.createXULElement("div"); + container.appendChild(host); + + const shadowRoot = host.attachShadow({mode}) + const button = document.createXULElement("button"); + button.innerText = "Click Me"; + button.setAttribute("accesskey", "g"); + shadowRoot.appendChild(button); + + // Trigger frame construction which is constructed lazily on XUL Element. + button.getBoundingClientRect(); + + let isClickFired = false; + button.addEventListener("click", function(e) { + isClickFired = true; + }, { once: true }); + + pressAccessKey("g"); + ok(isClickFired, `button element with accesskey in the shadow tree of ${mode} mode`); + + host.remove(); + }); +} + +testAccesskeyInShadowTree("open"); +testAccesskeyInShadowTree("closed"); + +]]> +</script> +</window> diff --git a/dom/tests/mochitest/webcomponents/upgrade_tests.js b/dom/tests/mochitest/webcomponents/upgrade_tests.js new file mode 100644 index 0000000000..85cff984f0 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/upgrade_tests.js @@ -0,0 +1,128 @@ +function test_upgrade(f, msg) { + // Run upgrading test on an element created by HTML parser. + test_with_new_window(function (testWindow, testMsg) { + let elementParser = testWindow.document.querySelector("unresolved-element"); + f(testWindow, elementParser, testMsg); + }, msg + " (HTML parser)"); + + // Run upgrading test on an element created by document.createElement. + test_with_new_window(function (testWindow, testMsg) { + let element = testWindow.document.createElement("unresolved-element"); + testWindow.document.documentElement.appendChild(element); + f(testWindow, element, testMsg); + }, msg + " (document.createElement)"); +} + +// Test cases + +test_upgrade(function (testWindow, testElement, msg) { + class MyCustomElement extends testWindow.HTMLElement {} + testWindow.customElements.define("unresolved-element", MyCustomElement); + SimpleTest.is( + Object.getPrototypeOf(Cu.waiveXrays(testElement)), + MyCustomElement.prototype, + msg + ); +}, "Custom element must be upgraded if there is a matching definition"); + +test_upgrade(function (testWindow, testElement, msg) { + testElement.remove(); + class MyCustomElement extends testWindow.HTMLElement {} + testWindow.customElements.define("unresolved-element", MyCustomElement); + SimpleTest.is( + Object.getPrototypeOf(testElement), + testWindow.HTMLElement.prototype, + msg + ); +}, "Custom element must not be upgraded if it has been removed from tree"); + +test_upgrade(function (testWindow, testElement, msg) { + let exceptionToThrow = { name: "exception thrown by a custom constructor" }; + class ThrowCustomElement extends testWindow.HTMLElement { + constructor() { + super(); + if (exceptionToThrow) { + throw exceptionToThrow; + } + } + } + + let uncaughtError; + window.onerror = function (message, url, lineNumber, columnNumber, error) { + uncaughtError = error; + return true; + }; + testWindow.customElements.define("unresolved-element", ThrowCustomElement); + + SimpleTest.is(uncaughtError.name, exceptionToThrow.name, msg); +}, "Upgrading must report an exception thrown by a custom element constructor"); + +test_upgrade(function (testWindow, testElement, msg) { + class InstantiatesItselfAfterSuper extends testWindow.HTMLElement { + constructor(doNotCreateItself) { + super(); + if (!doNotCreateItself) { + new InstantiatesItselfAfterSuper(true); + } + } + } + + let uncaughtError; + window.onerror = function (message, url, lineNumber, columnNumber, error) { + uncaughtError = error; + return true; + }; + testWindow.customElements.define( + "unresolved-element", + InstantiatesItselfAfterSuper + ); + + SimpleTest.is(uncaughtError.name, "TypeError", msg); +}, "Upgrading must report an TypeError when the top of the " + + "construction stack is marked AlreadyConstructed"); + +test_upgrade(function (testWindow, testElement, msg) { + class InstantiatesItselfBeforeSuper extends testWindow.HTMLElement { + constructor(doNotCreateItself) { + if (!doNotCreateItself) { + new InstantiatesItselfBeforeSuper(true); + } + super(); + } + } + + let uncaughtError; + window.onerror = function (message, url, lineNumber, columnNumber, error) { + uncaughtError = error; + return true; + }; + testWindow.customElements.define( + "unresolved-element", + InstantiatesItselfBeforeSuper + ); + + SimpleTest.is(uncaughtError.name, "TypeError", msg); +}, "Upgrading must report an TypeError when the top of the " + + "construction stack is marked AlreadyConstructed due to a custom element " + + "constructor constructing itself before super() call"); + +test_upgrade(function (testWindow, testElement, msg) { + class MyOtherElement extends testWindow.HTMLElement { + constructor() { + super(); + if (this == testElement) { + return testWindow.document.createElement("other-element"); + } + } + } + + let uncaughtError; + window.onerror = function (message, url, lineNumber, columnNumber, error) { + uncaughtError = error; + return true; + }; + testWindow.customElements.define("unresolved-element", MyOtherElement); + + SimpleTest.is(uncaughtError.name, "TypeError", msg); +}, "Upgrading must report an TypeError when the returned element is " + + "not SameValue as the upgraded element"); |