summaryrefslogtreecommitdiffstats
path: root/dom/tests/mochitest/webcomponents
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/tests/mochitest/webcomponents/chrome.toml24
-rw-r--r--dom/tests/mochitest/webcomponents/dummy_page.html10
-rw-r--r--dom/tests/mochitest/webcomponents/head.js26
-rw-r--r--dom/tests/mochitest/webcomponents/htmlconstructor_autonomous_tests.js107
-rw-r--r--dom/tests/mochitest/webcomponents/htmlconstructor_builtin_tests.js282
-rw-r--r--dom/tests/mochitest/webcomponents/inert_style.css10
-rw-r--r--dom/tests/mochitest/webcomponents/mochitest.toml90
-rw-r--r--dom/tests/mochitest/webcomponents/test_bug1017896.html32
-rw-r--r--dom/tests/mochitest/webcomponents/test_bug1269155.html95
-rw-r--r--dom/tests/mochitest/webcomponents/test_bug1276240.html44
-rw-r--r--dom/tests/mochitest/webcomponents/test_bug900724.html32
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_callback_innerhtml.html43
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_define.html129
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_define_parser.html61
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_ensure_custom_element.html55
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_get.html31
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_htmlconstructor.html42
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_htmlconstructor_chrome.html40
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_in_shadow.html129
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html432
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_namespace.html95
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_namespace.xhtml104
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_set_element_creation_callback.html159
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_stack.html138
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_template.html33
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_throw_on_dynamic_markup_insertion.html66
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_uncatchable_exception.html37
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_upgrade.html41
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_upgrade_chrome.html43
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_when_defined.html140
-rw-r--r--dom/tests/mochitest/webcomponents/test_declarative_shadowdom_failure_log.html37
-rw-r--r--dom/tests/mochitest/webcomponents/test_declarative_shadowdom_reentry.html25
-rw-r--r--dom/tests/mochitest/webcomponents/test_detached_style.html35
-rw-r--r--dom/tests/mochitest/webcomponents/test_document_adoptnode.html42
-rw-r--r--dom/tests/mochitest/webcomponents/test_document_importnode.html42
-rw-r--r--dom/tests/mochitest/webcomponents/test_event_composed.html72
-rw-r--r--dom/tests/mochitest/webcomponents/test_event_retarget.html153
-rw-r--r--dom/tests/mochitest/webcomponents/test_event_stopping.html174
-rw-r--r--dom/tests/mochitest/webcomponents/test_link_prefetch.html110
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowdom_active_pseudo_class.html62
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowdom_ime.html59
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot.html77
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_clonenode.html46
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html30
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_style.html85
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_style_order.html52
-rw-r--r--dom/tests/mochitest/webcomponents/test_style_fallback_content.html39
-rw-r--r--dom/tests/mochitest/webcomponents/test_template.html153
-rw-r--r--dom/tests/mochitest/webcomponents/test_template_xhtml.html46
-rw-r--r--dom/tests/mochitest/webcomponents/test_upgrade_page.html11
-rw-r--r--dom/tests/mochitest/webcomponents/test_xul_custom_element.xhtml386
-rw-r--r--dom/tests/mochitest/webcomponents/test_xul_shadowdom_accesskey.xhtml60
-rw-r--r--dom/tests/mochitest/webcomponents/upgrade_tests.js128
53 files changed, 4494 insertions, 0 deletions
diff --git a/dom/tests/mochitest/webcomponents/chrome.toml b/dom/tests/mochitest/webcomponents/chrome.toml
new file mode 100644
index 0000000000..b29df619c4
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/chrome.toml
@@ -0,0 +1,24 @@
+[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.toml b/dom/tests/mochitest/webcomponents/mochitest.toml
new file mode 100644
index 0000000000..8b9dad41d5
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/mochitest.toml
@@ -0,0 +1,90 @@
+[DEFAULT]
+support-files = [
+ "inert_style.css",
+ "dummy_page.html",
+ "head.js",
+]
+
+["test_bug900724.html"]
+
+["test_bug1017896.html"]
+
+["test_bug1269155.html"]
+
+["test_bug1276240.html"]
+
+["test_custom_element_callback_innerhtml.html"]
+
+["test_custom_element_define.html"]
+
+["test_custom_element_define_parser.html"]
+
+["test_custom_element_get.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_lifecycle.html"]
+
+["test_custom_element_set_element_creation_callback.html"]
+
+["test_custom_element_stack.html"]
+
+["test_custom_element_template.html"]
+
+["test_custom_element_throw_on_dynamic_markup_insertion.html"]
+
+["test_custom_element_uncatchable_exception.html"]
+run-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_when_defined.html"]
+
+["test_declarative_shadowdom_failure_log.html"]
+
+["test_declarative_shadowdom_reentry.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_link_prefetch.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_template.html"]
+
+["test_template_xhtml.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_declarative_shadowdom_failure_log.html b/dom/tests/mochitest/webcomponents/test_declarative_shadowdom_failure_log.html
new file mode 100644
index 0000000000..04a7bd3ead
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_declarative_shadowdom_failure_log.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1870055
+-->
+<head>
+ <title>Test for Logging of Declarative ShadowDOM attachment failure</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=1870055">Bug 1870055</a>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("load", () => {
+ const declarativeString = `<template shadowrootmode="open"></template>`;
+ const wrapper = document.createElement('div');
+ wrapper.attachShadow({mode: "open"});
+
+ SimpleTest.monitorConsole(SimpleTest.finish, [{ errorMessage: "Failed to attach Declarative Shadow DOM." }]);
+
+ const observer = new MutationObserver(e => {
+ if (e[0].addedNodes) {
+ SimpleTest.endMonitorConsole();
+ }
+ });
+ observer.observe(document.body, { childList: true });
+
+ wrapper.setHTMLUnsafe(declarativeString);
+ document.body.appendChild(wrapper);
+});
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_declarative_shadowdom_reentry.html b/dom/tests/mochitest/webcomponents/test_declarative_shadowdom_reentry.html
new file mode 100644
index 0000000000..fce2037afe
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_declarative_shadowdom_reentry.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1870055
+-->
+<head>
+ <title>Test for Logging of Declarative ShadowDOM attachment failure</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>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+document.addEventListener('DOMContentLoaded', async () => {
+ const node = document.createElement('div');
+ document.documentElement.appendChild(node);
+ document.addEventListener('DOMSubtreeModified', (e) => {
+ node.setHTMLUnsafe('b');
+ is(node.textContent, 'b', 'Re-entry into setHTMLUnsafe is valid');
+ SimpleTest.finish();
+ }, { once: true });
+ node.setHTMLUnsafe('a');
+});
+</script>
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..23abcab426
--- /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.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+);
+
+// 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..0e172cb70d
--- /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: "menupopup" });
+
+ 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, "menupopup", { 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, "menupopup", "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, "menupopup", "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(`<menupopup is="test-popup-extend" />`))
+ let element4 = document.querySelector("#content").lastChild;
+ is(element4.localName, "menupopup", "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"/>
+ <menupopup 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");