summaryrefslogtreecommitdiffstats
path: root/dom/tests/mochitest/webcomponents
diff options
context:
space:
mode:
Diffstat (limited to 'dom/tests/mochitest/webcomponents')
-rw-r--r--dom/tests/mochitest/webcomponents/inert_style.css10
-rw-r--r--dom/tests/mochitest/webcomponents/mochitest.ini48
-rw-r--r--dom/tests/mochitest/webcomponents/test_bug1017896.html32
-rw-r--r--dom/tests/mochitest/webcomponents/test_bug1176757.html40
-rw-r--r--dom/tests/mochitest/webcomponents/test_bug1276240.html55
-rw-r--r--dom/tests/mochitest/webcomponents/test_bug900724.html32
-rw-r--r--dom/tests/mochitest/webcomponents/test_content_element.html131
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_adopt_callbacks.html29
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_callback_innerhtml.html47
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_clone_callbacks.html54
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_clone_callbacks_extended.html40
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_get.html31
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_import_node_created_callback.html34
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_in_shadow.html132
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_register_invalid_callbacks.html72
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_when_defined.html140
-rw-r--r--dom/tests/mochitest/webcomponents/test_dest_insertion_points.html73
-rw-r--r--dom/tests/mochitest/webcomponents/test_dest_insertion_points_shadow.html68
-rw-r--r--dom/tests/mochitest/webcomponents/test_detached_style.html25
-rw-r--r--dom/tests/mochitest/webcomponents/test_document_adoptnode.html36
-rw-r--r--dom/tests/mochitest/webcomponents/test_document_importnode.html37
-rw-r--r--dom/tests/mochitest/webcomponents/test_document_register.html163
-rw-r--r--dom/tests/mochitest/webcomponents/test_document_register_base_queue.html48
-rw-r--r--dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html402
-rw-r--r--dom/tests/mochitest/webcomponents/test_document_register_parser.html47
-rw-r--r--dom/tests/mochitest/webcomponents/test_document_register_stack.html152
-rw-r--r--dom/tests/mochitest/webcomponents/test_document_shared_registry.html79
-rw-r--r--dom/tests/mochitest/webcomponents/test_dynamic_content_element_matching.html50
-rw-r--r--dom/tests/mochitest/webcomponents/test_event_dispatch.html458
-rw-r--r--dom/tests/mochitest/webcomponents/test_event_retarget.html147
-rw-r--r--dom/tests/mochitest/webcomponents/test_event_stopping.html168
-rw-r--r--dom/tests/mochitest/webcomponents/test_fallback_dest_insertion_points.html71
-rw-r--r--dom/tests/mochitest/webcomponents/test_link_prefetch.html110
-rw-r--r--dom/tests/mochitest/webcomponents/test_nested_content_element.html127
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadow_element.html173
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot.html83
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_host.html41
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html44
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_style.html83
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_style_multiple_shadow.html57
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_style_order.html42
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_youngershadowroot.html41
-rw-r--r--dom/tests/mochitest/webcomponents/test_style_fallback_content.html28
-rw-r--r--dom/tests/mochitest/webcomponents/test_template.html153
-rw-r--r--dom/tests/mochitest/webcomponents/test_template_custom_elements.html32
-rw-r--r--dom/tests/mochitest/webcomponents/test_template_xhtml.html46
-rw-r--r--dom/tests/mochitest/webcomponents/test_unresolved_pseudo_class.html99
47 files changed, 4110 insertions, 0 deletions
diff --git a/dom/tests/mochitest/webcomponents/inert_style.css b/dom/tests/mochitest/webcomponents/inert_style.css
new file mode 100644
index 000000000..3b005ede8
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/inert_style.css
@@ -0,0 +1,10 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This style is linked in test_shadowroot_inert_link to ensure
+ that link element in ShadowRoot is inert. */
+span {
+ padding-top: 10px;
+}
+
diff --git a/dom/tests/mochitest/webcomponents/mochitest.ini b/dom/tests/mochitest/webcomponents/mochitest.ini
new file mode 100644
index 000000000..496f7ea4d
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/mochitest.ini
@@ -0,0 +1,48 @@
+[DEFAULT]
+support-files =
+ inert_style.css
+
+[test_bug900724.html]
+[test_bug1017896.html]
+[test_bug1176757.html]
+[test_bug1276240.html]
+[test_content_element.html]
+[test_custom_element_adopt_callbacks.html]
+[test_custom_element_callback_innerhtml.html]
+[test_custom_element_clone_callbacks.html]
+[test_custom_element_clone_callbacks_extended.html]
+[test_custom_element_import_node_created_callback.html]
+[test_custom_element_in_shadow.html]
+[test_custom_element_register_invalid_callbacks.html]
+[test_custom_element_get.html]
+[test_custom_element_when_defined.html]
+[test_nested_content_element.html]
+[test_dest_insertion_points.html]
+[test_dest_insertion_points_shadow.html]
+[test_fallback_dest_insertion_points.html]
+[test_detached_style.html]
+[test_dynamic_content_element_matching.html]
+[test_document_adoptnode.html]
+[test_document_importnode.html]
+[test_document_register.html]
+[test_document_register_base_queue.html]
+[test_document_register_lifecycle.html]
+[test_document_register_parser.html]
+[test_document_register_stack.html]
+[test_document_shared_registry.html]
+[test_event_dispatch.html]
+[test_event_retarget.html]
+[test_event_stopping.html]
+[test_template.html]
+[test_template_xhtml.html]
+[test_template_custom_elements.html]
+[test_shadowroot.html]
+[test_shadowroot_inert_element.html]
+[test_shadowroot_host.html]
+[test_shadowroot_style.html]
+[test_shadowroot_style_multiple_shadow.html]
+[test_shadowroot_style_order.html]
+[test_shadowroot_youngershadowroot.html]
+[test_style_fallback_content.html]
+[test_unresolved_pseudo_class.html]
+[test_link_prefetch.html]
diff --git a/dom/tests/mochitest/webcomponents/test_bug1017896.html b/dom/tests/mochitest/webcomponents/test_bug1017896.html
new file mode 100644
index 000000000..befec7d6d
--- /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 type="text/javascript" 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_bug1176757.html b/dom/tests/mochitest/webcomponents/test_bug1176757.html
new file mode 100644
index 000000000..57a7a73b7
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_bug1176757.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1176757
+-->
+<head>
+ <title>Test for Bug 1176757</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" 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=1176757">Mozilla Bug 1176757</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1176757 **/
+var element = document.createElement("a");
+var shadowRoot = element.createShadowRoot();
+var thrownException = false;
+
+try {
+ shadowRoot.cloneNode();
+} catch(err) {
+ thrownException = err;
+}
+
+ok(thrownException !== false, "An exception should've been thrown");
+is(thrownException.name, "DataCloneError", "A DataCloneError exception should've been thrown");
+
+</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 000000000..33e532a6a
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_bug1276240.html
@@ -0,0 +1,55 @@
+<!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 type="text/javascript" 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);
+}
+
+function runTest() {
+ test();
+ SimpleTest.finish();
+}
+
+// test with webcomponents enabled
+test();
+
+// test with webcomponents disabled
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ { 'set': [["dom.webcomponents.enabled", false]]}, runTest);
+
+</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 000000000..3d9b9184f
--- /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 type="text/javascript" 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_content_element.html b/dom/tests/mochitest/webcomponents/test_content_element.html
new file mode 100644
index 000000000..39f0cf07d
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_content_element.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=806506
+-->
+<head>
+ <title>Test for HTMLContent element</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="grabme"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
+<script>
+// Create a ShadowRoot and append some nodes, containing an insertion point with a universal selector.
+var shadow = $("grabme").createShadowRoot();
+shadow.innerHTML = '<span><content id="point"></content></span>';
+
+// Get the insertion point from the ShadowRoot and check that child of host is distributed.
+// Insertion point should match everything because the selector set is empty.
+var insertionPoint = shadow.getElementById("point");
+$("grabme").innerHTML = '<div id="distme"></div>';
+var distNodes = insertionPoint.getDistributedNodes();
+is(distNodes[0], $("distme"), "Child of bound content should be distributed into insertion point with universal selector.");
+is(distNodes.length, 1, "Should only have one child distributed into insertion point.");
+
+// Add another node to bound content and make sure that the node list is static and does not change.
+var someSpan = document.createElement("span");
+$("grabme").appendChild(someSpan);
+is(distNodes.length, 1, "NodeList from getDistributedNodes should be static.");
+
+// Test content select.
+$("grabme").innerHTML = '<div id="first" class="tall"></div><div id="second" class="skinny"></div>';
+shadow.innerHTML = '<span><content select=".tall" id="point"></content></span>';
+var insertionPoint = shadow.getElementById("point");
+distNodes = insertionPoint.getDistributedNodes();
+is(distNodes.length, 1, "Insertion point should only match element with the 'tall' class.");
+is(distNodes[0], $("first"), "Insertion point should only match element with the 'tall' class.");
+
+// Get rid of the select attribute and check that the insertion point matches everything.
+insertionPoint.removeAttribute("select");
+is(insertionPoint.getDistributedNodes().length, 2, "After removing the 'select' attribute, the insertion point should match everything.");
+
+// Set an invalid selector and make sure that nothing is matched.
+insertionPoint.setAttribute("select", "div:first-child");
+is(insertionPoint.getDistributedNodes().length, 0, "Invalid selectors should match nothing.");
+
+// all compound selectors must only be permitted simple selectors.
+insertionPoint.setAttribute("select", "div:first-child, span");
+is(insertionPoint.getDistributedNodes().length, 0, "Invalid selectors should match nothing.");
+
+// Test multiple compound selectors.
+$("grabme").innerHTML = '<div id="first"></div><span id="second"></span><span data-match-me="pickme" id="third"></span>';
+insertionPoint.setAttribute("select", "span[data-match-me=pickme], div");
+distNodes = insertionPoint.getDistributedNodes();
+is(distNodes.length, 2, "Insertion point selector should only match two nodes.");
+is(distNodes[0], $("first"), "First child node should match selector.");
+is(distNodes[1], $("third"), "Third child node should match selector.");
+
+// Test select property
+insertionPoint.select = "#second, #third";
+distNodes = insertionPoint.getDistributedNodes();
+is(distNodes.length, 2, "Insertion point selector (set using property) should only match two nodes.");
+is(distNodes[0], $("second"), "First child node should match selector.");
+is(distNodes[1], $("third"), "Third child node should match selector.");
+is(insertionPoint.select, "#second, #third", "select property should be transparent.");
+
+// Empty set of selectors should match everything.
+insertionPoint.select = "";
+is(insertionPoint.getDistributedNodes().length, 3, "Empty set of selectors (set using property) should match everything.");
+
+// Remove insertion point and make sure that the point does not have any nodes distributed.
+$("grabme").innerHTML = '<div></div><span></span>';
+insertionPoint.removeAttribute("select");
+is(insertionPoint.getDistributedNodes().length, 2, "Insertion point with univeral selector should match two nodes.");
+var insertionParent = insertionPoint.parentNode;
+insertionParent.removeChild(insertionPoint);
+is(insertionPoint.getDistributedNodes().length, 0, "Insertion point should match no nodes after removal.");
+insertionParent.appendChild(insertionPoint);
+is(insertionPoint.getDistributedNodes().length, 2, "Insertion point should match two nodes after appending.");
+
+// Test multiple insertion points and check tree order distribution of points.
+// Append two divs and three spans into host.
+$("grabme").innerHTML = '<div></div><span></span><div></div><span></span><span></span>';
+shadow.innerHTML = '<content select="div" id="divpoint"></content><content select="div, span" id="allpoint"></content>';
+// Insertion point matching div
+var divPoint = shadow.getElementById("divpoint");
+// Insertion point matching span and div
+var allPoint = shadow.getElementById("allpoint");
+
+is(divPoint.getDistributedNodes().length, 2, "Two div nodes should be distributed into divPoint.");
+is(allPoint.getDistributedNodes().length, 3, "Remaining nodes should be distributed into allPoint.");
+
+shadow.removeChild(allPoint);
+is(divPoint.getDistributedNodes().length, 2, "Number of div distributed into insertion point should not change.");
+is(allPoint.getDistributedNodes().length, 0, "Removed insertion point should not have any nodes.");
+
+shadow.insertBefore(allPoint, divPoint);
+is(allPoint.getDistributedNodes().length, 5, "allPoint should have nodes distributed before divPoint.");
+is(divPoint.getDistributedNodes().length, 0, "divPoint should have no distributed nodes because they are all distributed to allPoint.");
+
+// Make sure that fallback content are in the distributed nodes.
+$("grabme").innerHTML = '<div id="one"></div><div id="two"></div>';
+shadow.innerHTML = '<content select="#nothing" id="point"><span id="fallback"></span></content>';
+insertionPoint = shadow.getElementById("point");
+is(insertionPoint.getDistributedNodes().length, 1, "There should be one distributed node from fallback content.");
+is(insertionPoint.getDistributedNodes()[0].id, "fallback", "Distributed node should be fallback content.");
+
+$("grabme").innerHTML = '';
+shadow.innerHTML = '<content select="div" id="point"><span id="one"></span><span id="two"></span></content>';
+insertionPoint = shadow.getElementById("point");
+// Make sure that two fallback nodes are distributed into the insertion point.
+is(insertionPoint.getDistributedNodes().length, 2, "There should be two distributed nodes from fallback content.");
+is(insertionPoint.getDistributedNodes()[0].id, "one", "First distributed node should have an ID of one.");
+is(insertionPoint.getDistributedNodes()[1].id, "two", "Second distributed node should have an ID of two.");
+
+// Append a node that gets matched by the insertion point, thus causing the fallback content to be removed.
+var matchingDiv = document.createElement("div");
+matchingDiv.id = "three";
+$("grabme").appendChild(matchingDiv);
+is(insertionPoint.getDistributedNodes().length, 1, "There should be one node distributed from the host.");
+is(insertionPoint.getDistributedNodes()[0].id, "three", "Node distriubted from host should have id of three.");
+
+// Remove the matching node from the host and make sure that the fallback content gets distributed.
+$("grabme").removeChild(matchingDiv);
+is(insertionPoint.getDistributedNodes().length, 2, "There should be two distributed nodes from fallback content.");
+is(insertionPoint.getDistributedNodes()[0].id, "one", "First distributed node should have an ID of one.");
+is(insertionPoint.getDistributedNodes()[1].id, "two", "Second distributed node should have an ID of two.");
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_adopt_callbacks.html b/dom/tests/mochitest/webcomponents/test_custom_element_adopt_callbacks.html
new file mode 100644
index 000000000..28597b7c6
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_adopt_callbacks.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1081039
+-->
+<head>
+ <title>Test callbacks for adopted custom elements.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<template id="template"><x-foo></x-foo></template>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1081039">Bug 1081039</a>
+<script>
+
+var p = Object.create(HTMLElement.prototype);
+p.createdCallback = function() {
+ ok(false, "Created callback should not be called for adopted node.");
+};
+
+document.registerElement("x-foo", { prototype: p });
+
+var template = document.getElementById("template");
+var adoptedFoo = document.adoptNode(template.content.firstChild);
+is(adoptedFoo.nodeName, "X-FOO");
+
+</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 000000000..94b02032f
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_callback_innerhtml.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1102502
+-->
+<head>
+ <title>Test for attached callback for element created in the document by the parser</title>
+ <script type="text/javascript" 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 attachedCallbackCount = 0;
+
+var p = Object.create(HTMLElement.prototype);
+
+p.createdCallback = function() {
+ ok(true, "createdCallback called.");
+};
+
+p.attachedCallback = function() {
+ ok(true, "attachedCallback should be called when the parser creates an element in the document.");
+ attachedCallbackCount++;
+ // attachedCallback should be called twice, once for the element created for innerHTML and
+ // once for the element created in this document.
+ if (attachedCallbackCount == 2) {
+ SimpleTest.finish();
+ }
+}
+
+document.registerElement("x-foo", { prototype: p });
+
+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_clone_callbacks.html b/dom/tests/mochitest/webcomponents/test_custom_element_clone_callbacks.html
new file mode 100644
index 000000000..eea9bafe0
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_clone_callbacks.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1081039
+-->
+<head>
+ <title>Test callbacks for cloned custom elements.</title>
+ <script type="text/javascript" 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=1081039">Bug 1081039</a>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+// Test to make sure created callback is called on clones that are upgraded and clones
+// created after registering the custom element.
+
+var callbackCalledOnUpgrade = false;
+var callbackCalledOnClone = false;
+
+var foo = document.createElement("x-foo");
+var fooClone = foo.cloneNode(true);
+
+var p = Object.create(HTMLElement.prototype);
+p.createdCallback = function() {
+ is(this.__proto__, p, "Correct prototype should be set on custom elements.");
+
+ if (this == fooClone) {
+ // Callback called for the element created before registering the custom element.
+ // Should be called on element upgrade.
+ is(callbackCalledOnUpgrade, false, "Upgrade should only be called once per clone.");
+ callbackCalledOnUpgrade = true;
+ } else if (this != foo) {
+ // Callback called for the element created after registering the custom element.
+ is(callbackCalledOnClone, false, "Upgrade should only be called once per clone.");
+ callbackCalledOnClone = true;
+ }
+
+ if (callbackCalledOnUpgrade && callbackCalledOnClone) {
+ SimpleTest.finish();
+ }
+};
+
+document.registerElement("x-foo", { prototype: p });
+
+var anotherFooClone = foo.cloneNode(true);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_clone_callbacks_extended.html b/dom/tests/mochitest/webcomponents/test_custom_element_clone_callbacks_extended.html
new file mode 100644
index 000000000..b3531b0ea
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_clone_callbacks_extended.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1081039
+-->
+<head>
+ <title>Test callbacks for cloned extended custom elements.</title>
+ <script type="text/javascript" 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=1081039">Bug 1081039</a>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+// Test to make sure created callback is called on clones created after
+// registering the custom element.
+
+var count = 0;
+var p = Object.create(HTMLButtonElement.prototype);
+p.createdCallback = function() { // should be called by createElement and cloneNode
+ is(this.__proto__, p, "Correct prototype should be set on custom elements.");
+
+ if (++count == 2) {
+ SimpleTest.finish();
+ }
+};
+
+document.registerElement("x-foo", { prototype: p, extends: "button" });
+var foo = document.createElement("button", {is: "x-foo"});
+is(foo.getAttribute("is"), "x-foo");
+
+var fooClone = foo.cloneNode(true);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</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 000000000..3920ea343
--- /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 type="text/javascript" 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_import_node_created_callback.html b/dom/tests/mochitest/webcomponents/test_custom_element_import_node_created_callback.html
new file mode 100644
index 000000000..f533b507c
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_import_node_created_callback.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1093680
+-->
+<head>
+ <title>Test created callback order for imported custom element.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<template id="template"><x-foo><span></span></x-foo></template>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1093680">Bug 1093680</a>
+<script>
+
+var fooProtoCreatedCallbackCalled = false;
+var fooProto = Object.create(HTMLElement.prototype);
+fooProto.createdCallback = function() {
+ ok(this.firstElementChild, "When the created callback is called, the element should already have a child because the callback should only be called after cloning all the contents.");
+ fooProtoCreatedCallbackCalled = true;
+};
+
+document.registerElement("x-foo", { prototype: fooProto });
+
+var template = document.getElementById("template");
+
+// Importing node will implicityly clone the conent in the main document.
+var adoptedFoo = document.importNode(template.content, true);
+
+ok(fooProtoCreatedCallbackCalled, "Created callback should be called after importing custom element into document");
+
+</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 000000000..504140648
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_in_shadow.html
@@ -0,0 +1,132 @@
+<!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="/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>
+<div id="container"></div>
+
+<script>
+
+// Test callback for custom element when used after registration.
+
+var createdCallbackCount = 0;
+var attachedCallbackCount = 0;
+var detachedCallbackCount = 0;
+var attributeChangedCallbackCount = 0;
+
+var p1 = Object.create(HTMLElement.prototype);
+
+p1.createdCallback = function() {
+ createdCallbackCount++;
+};
+
+p1.attachedCallback = function() {
+ attachedCallbackCount++;
+};
+
+p1.detachedCallback = function() {
+ detachedCallbackCount++;
+};
+
+p1.attributeChangedCallback = function(name, oldValue, newValue) {
+ attributeChangedCallbackCount++;
+};
+
+document.registerElement("x-foo", { prototype: p1 });
+
+var container = document.getElementById("container");
+var shadow = container.createShadowRoot();
+
+is(createdCallbackCount, 0, "createdCallback should not be called more than once in this test.");
+var customElem = document.createElement("x-foo");
+is(createdCallbackCount, 1, "createdCallback should be called after creating custom element.");
+
+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(attachedCallbackCount, 0, "attachedCallback should not be called on an element that is not in a document/composed document.");
+shadow.appendChild(customElem);
+is(attachedCallbackCount, 1, "attachedCallback should be called after attaching custom element to the composed document.");
+
+is(detachedCallbackCount, 0, "detachedCallback should not be called without detaching custom element.");
+shadow.removeChild(customElem);
+is(detachedCallbackCount, 1, "detachedCallback should be called after detaching custom element from the composed document.");
+
+// Test callback for custom element already in the composed doc when created.
+
+createdCallbackCount = 0;
+attachedCallbackCount = 0;
+detachedCallbackCount = 0;
+attributeChangedCallbackCount = 0;
+
+shadow.innerHTML = "<x-foo></x-foo>";
+is(createdCallbackCount, 1, "createdCallback should be called after creating a custom element.");
+is(attachedCallbackCount, 1, "attachedCallback should be called after creating an element in the composed document.");
+
+shadow.innerHTML = "";
+is(detachedCallbackCount, 1, "detachedCallback 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.
+
+createdCallbackCount = 0;
+attachedCallbackCount = 0;
+detachedCallbackCount = 0;
+attributeChangedCallbackCount = 0;
+
+var host = document.createElement("div");
+shadow = host.createShadowRoot();
+customElem = document.createElement("x-foo");
+
+is(attachedCallbackCount, 0, "attachedCallback should not be called on newly created element.");
+shadow.appendChild(customElem);
+is(attachedCallbackCount, 0, "attachedCallback should not be called on attaching to a tree that is not in the composed document.");
+
+is(attachedCallbackCount, 0, "detachedCallback should not be called.");
+shadow.removeChild(customElem);
+is(detachedCallbackCount, 0, "detachedCallback should not be called when detaching from a tree that is not in the composed document.");
+
+shadow.appendChild(customElem);
+is(attachedCallbackCount, 0, "attachedCallback should still not be called after reattaching to a shadow tree that is not in the composed document.");
+
+container.appendChild(host);
+is(attachedCallbackCount, 1, "attachedCallback should be called after host is inserted into document.");
+
+container.removeChild(host);
+is(detachedCallbackCount, 1, "detachedCallback should be called after host is removed from document.");
+
+// Test callback for custom element for upgraded element.
+
+createdCallbackCount = 0;
+attachedCallbackCount = 0;
+detachedCallbackCount = 0;
+attributeChangedCallbackCount = 0;
+
+shadow = container.shadowRoot;
+shadow.innerHTML = "<x-bar></x-bar>";
+
+var p2 = Object.create(HTMLElement.prototype);
+
+p2.createdCallback = function() {
+ createdCallbackCount++;
+};
+
+p2.attachedCallback = function() {
+ attachedCallbackCount++;
+};
+
+document.registerElement("x-bar", { prototype: p2 });
+is(createdCallbackCount, 1, "createdCallback should be called after registering element.");
+is(attachedCallbackCount, 1, "attachedCallback should be called after upgrading element in composed document.");
+
+</script>
+
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_register_invalid_callbacks.html b/dom/tests/mochitest/webcomponents/test_custom_element_register_invalid_callbacks.html
new file mode 100644
index 000000000..a349f4aa5
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_register_invalid_callbacks.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1275835
+-->
+<head>
+ <title>Test registering invalid lifecycle callbacks for custom elements.</title>
+ <script type="text/javascript" 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=1275835">Bug 1275835</a>
+<iframe id="iframe"></iframe>
+<script>
+
+// Use window from iframe to isolate the test.
+const testWindow = iframe.contentDocument.defaultView;
+
+// This is for backward compatibility.
+// We should do the same checks for the callbacks from v0 spec.
+[
+ 'createdCallback',
+ 'attachedCallback',
+ 'detachedCallback',
+ 'attributeChangedCallback',
+].forEach(callback => {
+ var c = class {};
+ var p = c.prototype;
+
+ // Test getting callback throws exception.
+ Object.defineProperty(p, callback, {
+ get() {
+ const e = new Error('this is rethrown');
+ e.name = 'rethrown';
+ throw e;
+ }
+ });
+
+ SimpleTest.doesThrow(() => {
+ testWindow.document.registerElement(`test-register-${callback}-rethrown`,
+ { prototype: p });
+ }, `document.registerElement should throw exception if prototype.${callback} throws`);
+
+ SimpleTest.doesThrow(() => {
+ testWindow.customElements.define(`test-define-${callback}-rethrown`, c);
+ }, `customElements.define should throw exception if constructor.${callback} throws`);
+
+ // Test callback is not callable.
+ [
+ { name: 'null', value: null },
+ { name: 'object', value: {} },
+ { name: 'integer', value: 1 },
+ ].forEach(data => {
+ var c = class {};
+ var p = c.prototype;
+
+ p[callback] = data.value;
+
+ SimpleTest.doesThrow(() => {
+ testWindow.document.registerElement(`test-register-${callback}-${data.name}`,
+ { prototype: p });
+ }, `document.registerElement should throw exception if ${callback} is ${data.name}`);
+
+ SimpleTest.doesThrow(() => {
+ testWindow.customElements.define(`test-define-${callback}-${data.name}`, c);
+ }, `customElements.define should throw exception if ${callback} is ${data.name}`);
+ });
+});
+
+</script>
+</body>
+</html>
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 000000000..a71530ae0
--- /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 type="text/javascript" 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_dest_insertion_points.html b/dom/tests/mochitest/webcomponents/test_dest_insertion_points.html
new file mode 100644
index 000000000..2d4a92ed2
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_dest_insertion_points.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=999999
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 999999</title>
+ <script type="application/javascript" 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=999999">Mozilla Bug 999999</a>
+<p id="display"></p>
+<div id="content">
+<div id="shadowhost">
+</div>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+
+/** Test for Bug 999999 **/
+var host = document.getElementById("shadowhost");
+
+// Test destination insertion points of node distributed to content element.
+var shadowRoot = host.createShadowRoot();
+shadowRoot.innerHTML = '<div id="innerhost"><content id="innercontent" select=".red"></content></div>';
+var innerContent = shadowRoot.getElementById("innercontent");
+
+var span = document.createElement("span");
+span.setAttribute("class", "red blue");
+is(host.getDestinationInsertionPoints().length, 0, "Destination insertion points should be empty when not being distributed.");
+
+host.appendChild(span);
+
+is(span.getDestinationInsertionPoints().length, 1, "Destination insertion points should only contain a single content insertion point.");
+is(span.getDestinationInsertionPoints()[0], innerContent, "Content element should contain destination insertion point.");
+
+// Test destination insertion points of redistributed node.
+var innerHost = shadowRoot.getElementById("innerhost");
+var innerShadowRoot = innerHost.createShadowRoot();
+innerShadowRoot.innerHTML = '<content id="innerinnercontent" select=".blue"></content>';
+
+var innerInnerContent = innerShadowRoot.getElementById("innerinnercontent");
+
+is(span.getDestinationInsertionPoints().length, 2, "Redistributed node should have 2 destination insertion points.");
+is(span.getDestinationInsertionPoints()[1], innerInnerContent, "Nested content insertion point should be in list of destination insertion points.");
+
+// Test destination insertion points after removing reprojection onto second content element.
+span.setAttribute("class", "red");
+is(span.getDestinationInsertionPoints().length, 1, "Destination insertion points should only contain 1 insertion point after removing reprojection.");
+is(span.getDestinationInsertionPoints()[0], innerContent, "First content element should be only insertion point after removing reprojection.");
+
+// Test destination insertion points after removing the projected content from the host.
+host.removeChild(span);
+is(span.getDestinationInsertionPoints().length, 0, "Destination insertion points should be empty after being removed from the shadow host.");
+
+// Test destination insertion points of distributed content after removing insertion point.
+var div = document.createElement("div");
+div.setAttribute("class", "red blue");
+host.appendChild(div);
+
+is(div.getDestinationInsertionPoints().length, 2, "Div should be distributed into 2 insertion points.");
+
+innerShadowRoot.removeChild(innerInnerContent);
+
+is(div.getDestinationInsertionPoints().length, 1, "Div should be distributed into insertion point in one ShadowRoot.");
+is(div.getDestinationInsertionPoints()[0], innerContent, "Destination insertion points should only contain content insertion point in first ShadowRoot.");
+
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_dest_insertion_points_shadow.html b/dom/tests/mochitest/webcomponents/test_dest_insertion_points_shadow.html
new file mode 100644
index 000000000..75286463e
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_dest_insertion_points_shadow.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=999999
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 999999</title>
+ <script type="application/javascript" 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=999999">Mozilla Bug 999999</a>
+<p id="display"></p>
+<div id="content">
+<div id="shadowhost"></div>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+
+/** Test for Bug 999999 **/
+var host = document.getElementById("shadowhost");
+
+// Test destination insertion points of node distributed to shadow element.
+var olderShadowRoot = host.createShadowRoot();
+var youngerShadowRoot = host.createShadowRoot();
+
+var shadowElem = document.createElement("shadow");
+youngerShadowRoot.appendChild(shadowElem);
+
+var span = document.createElement("span");
+olderShadowRoot.appendChild(span);
+
+is(span.getDestinationInsertionPoints().length, 1, "Child of ShadowRoot should be distributed to shadow insertion point.");
+is(span.getDestinationInsertionPoints()[0], shadowElem, "Shadow element should be in destination insertion point list.");
+
+// Test destination insertion points of node removed from tree.
+olderShadowRoot.removeChild(span);
+is(span.getDestinationInsertionPoints().length, 0, "Node removed from tree should no longer be distributed.");
+
+// Test destination insertion points of fallback content being reprojected into a shadow element.
+var content = document.createElement("content");
+var fallback = document.createElement("span");
+
+content.appendChild(fallback);
+olderShadowRoot.appendChild(content);
+
+is(fallback.getDestinationInsertionPoints().length, 2, "The fallback content should have 2 destination insertion points, the parent content and the shadow element to which it is reprojected.");
+is(fallback.getDestinationInsertionPoints()[0], content, "First destination of the fallback content should be the parent content element.");
+is(fallback.getDestinationInsertionPoints()[1], shadowElem, "Second destination of the fallback content should be the shadow element to which the element is reprojected.");
+
+// Test destination insertion points of fallback content being removed from tree.
+content.removeChild(fallback);
+is(fallback.getDestinationInsertionPoints().length, 0, "The content should no longer be distributed to any nodes because it is no longer fallback content.");
+
+// Test destination insertion points of distributed content after removing shadow insertion point.
+var div = document.createElement("div");
+olderShadowRoot.appendChild(div);
+is(div.getDestinationInsertionPoints().length, 1, "Children in older shadow root should be distributed to shadow insertion point.");
+is(div.getDestinationInsertionPoints()[0], shadowElem, "Destination insertion point should include shadow element.");
+
+youngerShadowRoot.removeChild(shadowElem);
+is(div.getDestinationInsertionPoints().length, 0, "Destination insertion points should be empty after removing shadow element.");
+
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_detached_style.html b/dom/tests/mochitest/webcomponents/test_detached_style.html
new file mode 100644
index 000000000..9c52e2d29
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_detached_style.html
@@ -0,0 +1,25 @@
+<!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="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="grabme"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1062578">Bug 1062578</a>
+<script>
+var host = document.createElement("div");
+var shadow = host.createShadowRoot();
+shadow.innerHTML = '<style> #inner { height: 200px; } </style><div id="inner">Hello</div>';
+
+grabme.appendChild(host);
+
+var inner = shadow.getElementById("inner");
+is(getComputedStyle(inner, null).getPropertyValue("height"), "200px", "Style in shadow root should take effect.");
+</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 000000000..b00bb4fac
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_adoptnode.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1177991
+-->
+<head>
+ <title>Test for Bug 1177991</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" 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">
+
+var thrownException = false;
+var shadowRoot = document.createElement('a').createShadowRoot();
+
+try {
+ document.adoptNode(shadowRoot);
+} catch(err) {
+ thrownException = err;
+}
+
+ok(thrownException !== false, "A HierarchyRequestError");
+is(thrownException.name, "HierarchyRequestError", "A HierarchyRequestError should've been thrown");
+
+</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 000000000..f9042fddf
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_importnode.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1177914
+-->
+<head>
+ <title>Test for Bug 1177914</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" 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">
+
+var thrownException = false;
+var shadowRoot = document.createElement('a').createShadowRoot();
+
+try {
+ document.importNode(shadowRoot);
+} catch(err) {
+ thrownException = err;
+}
+
+
+ok(thrownException !== false, "An exception should've been thrown");
+is(thrownException.name, "NotSupportedError", "A NotSupportedError exception should've been thrown");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_document_register.html b/dom/tests/mochitest/webcomponents/test_document_register.html
new file mode 100644
index 000000000..a9c194b60
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_register.html
@@ -0,0 +1,163 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783129
+-->
+<head>
+ <title>Test for document.registerElement using custom prototype</title>
+ <script type="text/javascript" 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 testRegisterExtend(tag, extend, proto, expectException) {
+ try {
+ document.registerElement(tag, { prototype: proto, extends: extend });
+ ok(!expectException, "Registered " + tag + " extending " + extend + " containing " + proto + " in proto chain.");
+ } catch (ex) {
+ ok(expectException, "Did not register " + tag + " extending " + extend + " containing " + proto + " in proto chain.");
+ }
+}
+
+function testRegisterSimple(tag, proto, expectException) {
+ try {
+ document.registerElement(tag, { prototype: proto });
+ ok(!expectException, "Registered " + tag + " containing " + proto + " in proto chain.");
+ } catch (ex) {
+ ok(expectException, "Did not register " + tag + " containing " + proto + " in proto chain.");
+ }
+}
+
+function startTest() {
+ // Test registering some simple prototypes.
+ testRegisterSimple("x-html-obj-elem", Object.create(HTMLElement.prototype), false);
+ testRegisterSimple("x-html-obj-p", Object.create(HTMLParagraphElement.prototype), false);
+
+ // If prototype is an interface prototype object for any interface object,
+ // registration will throw.
+ testRegisterSimple("x-html-elem", HTMLElement.prototype, true);
+ testRegisterSimple("x-html-select", HTMLSelectElement.prototype, true);
+ testRegisterSimple("some-elem", HTMLElement.prototype, true);
+ testRegisterSimple("x-html-p", HTMLParagraphElement.prototype, true);
+ testRegisterSimple("x-html-span", HTMLSpanElement.prototype, true);
+
+ // 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.");
+
+ // Registering without a prototype should automatically create one inheriting from HTMLElement.
+ testRegisterSimple("x-elem-no-proto", null, false);
+
+ var simpleProto = Object.create(HTMLElement.prototype);
+ testRegisterSimple("x-elem-simple-proto", simpleProto, false);
+
+ // Test registering some invalid prototypes.
+ testRegisterSimple("x-invalid-number", 42, true);
+ testRegisterSimple("x-invalid-boolean", false, true);
+ testRegisterSimple("x-invalid-float", 1.0, true);
+ // A prototype with a non-configurable "constructor" property must throw.
+ var nonConfigProto = Object.create(HTMLElement.prototype,
+ { constructor: { configurable: false, value: function() {} } });
+ testRegisterSimple("x-non-config-proto", nonConfigProto, true);
+
+ // Test invalid custom element names.
+ testRegisterSimple("invalid", Object.create(HTMLElement.prototype), true);
+ testRegisterSimple("annotation-xml", Object.create(HTMLElement.prototype), true);
+ testRegisterSimple("color-profile", Object.create(HTMLElement.prototype), true);
+ testRegisterSimple("font-face", Object.create(HTMLElement.prototype), true);
+ testRegisterSimple("font-face-src", Object.create(HTMLElement.prototype), true);
+ testRegisterSimple("font-face-uri", Object.create(HTMLElement.prototype), true);
+ testRegisterSimple("font-face-format", Object.create(HTMLElement.prototype), true);
+ testRegisterSimple("font-face-name", Object.create(HTMLElement.prototype), true);
+ testRegisterSimple("missing-glyph", Object.create(HTMLElement.prototype), true);
+
+ // Test registering elements that extend from an existing element.
+ testRegisterExtend("x-extend-span", "span", Object.create(HTMLElement.prototype), false);
+ testRegisterExtend("x-extend-span-caps", "SPAN", Object.create(HTMLElement.prototype), false);
+
+ // Test registering elements that extend from a non-existing element.
+ testRegisterExtend("x-extend-span-nonexist", "nonexisting", Object.create(HTMLElement.prototype), true);
+
+ // Test registration with duplicate type.
+ testRegisterSimple("x-dupe-me", Object.create(HTMLElement.prototype), false);
+ testRegisterSimple("x-dupe-me", Object.create(HTMLElement.prototype), true);
+ testRegisterSimple("X-DUPE-ME", Object.create(HTMLElement.prototype), true);
+ testRegisterSimple("x-dupe-me", null, true);
+ testRegisterExtend("x-dupe-me", "span", Object.create(HTMLElement.prototype), true);
+
+ // document.createElement with extended type.
+ var extendedProto = Object.create(HTMLButtonElement.prototype);
+ var buttonConstructor = document.registerElement("x-extended-button", { prototype: extendedProto, 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__, extendedProto, "Created element should have the prototype of the extended type.");
+ is(extendedButton.getAttribute("is"), "x-extended-button", "The |is| attribute of the created element should be the extended type.");
+ is(extendedButton.type, "submit", "Created element should be a button with type of \"submit\"");
+
+ // document.createElementNS with different namespace than definition.
+ try {
+ var svgButton = document.createElementNS("http://www.w3.org/2000/svg", "button", {is: "x-extended-button"});
+ ok(false, "An exception should've been thrown");
+ } catch(err) {
+ is(err.name, "NotFoundError", "A NotFoundError exception should've been thrown");
+ }
+
+ // document.createElementNS with no namespace.
+ try {
+ var noNamespaceButton = document.createElementNS("", "button", {is: "x-extended-button"});
+ ok(false, "An exception should've been thrown");
+ } catch(err) {
+ is(err.name, "NotFoundError", "A NotFoundError exception should've been thrown");
+ }
+
+ // document.createElement with non-existant extended type.
+ try {
+ var normalButton = document.createElement("button", {is: "x-non-existant"});
+ ok(false, "An exception should've been thrown");
+ } catch(err) {
+ is(err.name, "NotFoundError", "A NotFoundError exception should've been thrown");
+ }
+
+ // document.createElement with exteneded type that does not match with local name of element.
+ try {
+ var normalDiv = document.createElement("div", {is: "x-extended-button"});
+ ok(false, "An exception should've been thrown");
+ } catch(err) {
+ is(err.name, "NotFoundError", "A NotFoundError exception should've been thrown");
+ }
+
+ // Custom element constructor.
+ var constructedButton = new buttonConstructor();
+ is(constructedButton.tagName, "BUTTON", "Created element should have local name of BUTTON");
+ is(constructedButton.__proto__, extendedProto, "Created element should have the prototype of the extended type.");
+ is(constructedButton.getAttribute("is"), "x-extended-button", "The |is| attribute of the created element should be the extended type.");
+
+ // document.createElement with different namespace than definition for extended element.
+ try {
+ var htmlText = document.createElement("text", {is: "x-extended-text"});
+ ok(false, "An exception should've been thrown");
+ } catch(err) {
+ is(err.name, "NotFoundError", "A NotFoundError exception should've been thrown");
+ }
+
+ // Try creating an element with a custom element name, but not in the html namespace.
+ var htmlNamespaceProto = Object.create(HTMLElement.prototype);
+ document.registerElement("x-in-html-namespace", { prototype: htmlNamespaceProto });
+ var wrongNamespaceElem = document.createElementNS("http://www.w3.org/2000/svg", "x-in-html-namespace");
+ isnot(wrongNamespaceElem.__proto__, htmlNamespaceProto, "Definition for element in html namespace should not apply to SVG elements.");
+}
+
+startTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_document_register_base_queue.html b/dom/tests/mochitest/webcomponents/test_document_register_base_queue.html
new file mode 100644
index 000000000..c839857b4
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_register_base_queue.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783129
+-->
+<head>
+ <title>Test for document.registerElement lifecycle callback</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script>
+var p = Object.create(HTMLElement.prototype);
+
+var createdCallbackCallCount = 0;
+
+// By the time the base element queue is processed via the microtask,
+// both x-hello elements should be in the document.
+p.createdCallback = function() {
+ var one = document.getElementById("one");
+ var two = document.getElementById("two");
+ isnot(one, null, "First x-hello element should be in the tree.");
+ isnot(two, null, "Second x-hello element should be in the tree.");
+ createdCallbackCallCount++;
+ if (createdCallbackCallCount == 2) {
+ SimpleTest.finish();
+ }
+
+ if (createdCallbackCallCount > 2) {
+ ok(false, "Created callback called too much.");
+ }
+};
+
+p.attributeChangedCallback = function(name, oldValue, newValue) {
+ ok(false, "Attribute changed callback should not be called because callbacks should not be queued until created callback invoked.");
+};
+
+document.registerElement("x-hello", { prototype: p });
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a>
+<x-hello id="one"></x-hello>
+<x-hello id="two"></x-hello>
+<script>
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html b/dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html
new file mode 100644
index 000000000..9db9afbf4
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html
@@ -0,0 +1,402 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783129
+-->
+<head>
+ <title>Test for document.registerElement lifecycle callback</title>
+ <script type="text/javascript" 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 registering element type that is already in the document.
+// create element in document -> register -> remove from document
+function testRegisterUnresolved() {
+ var helloElem = document.getElementById("hello");
+
+ var createdCallbackCalled = false;
+ var attachedCallbackCalled = false;
+ var detachedCallbackCalled = false;
+
+ var p = Object.create(HTMLElement.prototype);
+ p.createdCallback = function() {
+ is(helloElem.__proto__, p, "Prototype should be adjusted just prior to invoking the created callback.");
+ is(createdCallbackCalled, false, "Created callback should only be called once in this tests.");
+ is(this, helloElem, "The 'this' value should be the custom element.");
+ createdCallbackCalled = true;
+ };
+
+ p.attachedCallback = function() {
+ is(createdCallbackCalled, true, "Created callback should be called before attached");
+ is(attachedCallbackCalled, false, "attached callback should only be called once in this test.");
+ is(this, helloElem, "The 'this' value should be the custom element.");
+ attachedCallbackCalled = true;
+ };
+
+ p.detachedCallback = function() {
+ is(attachedCallbackCalled, true, "attached callback should be called before detached");
+ is(detachedCallbackCalled, false, "detached callback should only be called once in this test.");
+ detachedCallbackCalled = true;
+ is(this, helloElem, "The 'this' value should be the custom element.");
+ runNextTest();
+ };
+
+ p.attributeChangedCallback = function(name, oldValue, newValue) {
+ ok(false, "attributeChanged callback should never be called in this test.");
+ };
+
+ document.registerElement("x-hello", { prototype: p });
+ is(createdCallbackCalled, true, "created callback should be called when control returns to script from user agent code");
+
+ // Remove element from document to trigger detached callback.
+ container.removeChild(helloElem);
+}
+
+// Tests callbacks after registering an extended element type that is already in the document.
+// create element in document -> register -> remove from document
+function testRegisterUnresolvedExtended() {
+ var buttonElem = document.getElementById("extbutton");
+
+ var createdCallbackCalled = false;
+ var attachedCallbackCalled = false;
+ var detachedCallbackCalled = false;
+
+ var p = Object.create(HTMLButtonElement.prototype);
+ p.createdCallback = function() {
+ is(buttonElem.__proto__, p, "Prototype should be adjusted just prior to invoking the created callback.");
+ is(createdCallbackCalled, false, "Created callback should only be called once in this tests.");
+ is(this, buttonElem, "The 'this' value should be the custom element.");
+ createdCallbackCalled = true;
+ };
+
+ p.attachedCallback = function() {
+ is(createdCallbackCalled, true, "Created callback should be called before attached");
+ is(attachedCallbackCalled, false, "attached callback should only be called once in this test.");
+ is(this, buttonElem, "The 'this' value should be the custom element.");
+ attachedCallbackCalled = true;
+ };
+
+ p.detachedCallback = function() {
+ is(attachedCallbackCalled, true, "attached callback should be called before detached");
+ is(detachedCallbackCalled, false, "detached callback should only be called once in this test.");
+ detachedCallbackCalled = true;
+ is(this, buttonElem, "The 'this' value should be the custom element.");
+ runNextTest();
+ };
+
+ p.attributeChangedCallback = function(name, oldValue, newValue) {
+ ok(false, "attributeChanged callback should never be called in this test.");
+ };
+
+ document.registerElement("x-button", { prototype: p, extends: "button" });
+ is(createdCallbackCalled, true, "created callback should be called when control returns to script from user agent code");
+
+ // Remove element from document to trigger detached callback.
+ container.removeChild(buttonElem);
+}
+
+function testInnerHTML() {
+ var createdCallbackCalled = false;
+
+ var p = Object.create(HTMLElement.prototype);
+ p.createdCallback = function() {
+ is(createdCallbackCalled, false, "created callback should only be called once in this test.");
+ createdCallbackCalled = true;
+ };
+
+ document.registerElement("x-inner-html", { prototype: p });
+ var div = document.createElement(div);
+ div.innerHTML = '<x-inner-html></x-inner-html>';
+ is(createdCallbackCalled, true, "created callback should be called after setting innerHTML.");
+ runNextTest();
+}
+
+function testInnerHTMLExtended() {
+ var createdCallbackCalled = false;
+
+ var p = Object.create(HTMLButtonElement.prototype);
+ p.createdCallback = function() {
+ is(createdCallbackCalled, false, "created callback should only be called once in this test.");
+ createdCallbackCalled = true;
+ };
+
+ document.registerElement("x-inner-html-extended", { prototype: p, extends: "button" });
+ var div = document.createElement(div);
+ div.innerHTML = '<button is="x-inner-html-extended"></button>';
+ is(createdCallbackCalled, true, "created callback should be called after setting innerHTML.");
+ runNextTest();
+}
+
+function testInnerHTMLUpgrade() {
+ var createdCallbackCalled = false;
+
+ var div = document.createElement(div);
+ div.innerHTML = '<x-inner-html-upgrade></x-inner-html-upgrade>';
+
+ var p = Object.create(HTMLElement.prototype);
+ p.createdCallback = function() {
+ is(createdCallbackCalled, false, "created callback should only be called once in this test.");
+ createdCallbackCalled = true;
+ };
+
+ document.registerElement("x-inner-html-upgrade", { prototype: p });
+ is(createdCallbackCalled, true, "created callback should be called after registering.");
+ runNextTest();
+}
+
+function testInnerHTMLExtendedUpgrade() {
+ var createdCallbackCalled = false;
+
+ var div = document.createElement(div);
+ div.innerHTML = '<button is="x-inner-html-extended-upgrade"></button>';
+
+ var p = Object.create(HTMLButtonElement.prototype);
+ p.createdCallback = function() {
+ is(createdCallbackCalled, false, "created callback should only be called once in this test.");
+ createdCallbackCalled = true;
+ };
+
+ document.registerElement("x-inner-html-extended-upgrade", { prototype: p, extends: "button" });
+ is(createdCallbackCalled, true, "created callback should be called after registering.");
+ runNextTest();
+}
+
+// Test callback when creating element after registering an element type.
+// register -> create element -> insert into document -> remove from document
+function testRegisterResolved() {
+ var createdCallbackCalled = false;
+ var attachedCallbackCalled = false;
+ var detachedCallbackCalled = false;
+
+ var createdCallbackThis;
+
+ var p = Object.create(HTMLElement.prototype);
+ p.createdCallback = function() {
+ is(createdCallbackCalled, false, "Created callback should only be called once in this test.");
+ createdCallbackThis = this;
+ createdCallbackCalled = true;
+ };
+
+ p.attachedCallback = function() {
+ is(createdCallbackCalled, true, "created callback should be called before attached callback.");
+ is(attachedCallbackCalled, false, "attached callback should only be called on in this test.");
+ is(this, createdElement, "The 'this' value should be the custom element.");
+ attachedCallbackCalled = true;
+ };
+
+ p.detachedCallback = function() {
+ is(attachedCallbackCalled, true, "attached callback should be called before detached");
+ is(detachedCallbackCalled, false, "detached callback should only be called once in this test.");
+ is(this, createdElement, "The 'this' value should be the custom element.");
+ detachedCallbackCalled = true;
+ runNextTest();
+ };
+
+ p.attributeChangedCallback = function() {
+ ok(false, "attributeChanged callback should never be called in this test.");
+ };
+
+ document.registerElement("x-resolved", { prototype: p });
+ is(createdCallbackCalled, false, "Created callback should not be called when custom element instance has not been created.");
+
+ var createdElement = document.createElement("x-resolved");
+ is(createdCallbackThis, createdElement, "The 'this' value in the created callback should be the custom element.");
+ is(createdElement.__proto__, p, "Prototype of custom element should be the registered 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 p = Object.create(HTMLElement.prototype);
+ var callbackCalled = false;
+ p.attributeChangedCallback = function(name, oldValue, newValue) {
+ is(callbackCalled, false, "Callback should only be called once in this test.");
+ callbackCalled = true;
+ runNextTest();
+ };
+
+ document.registerElement("x-test-callback", { prototype: p });
+
+ p.attributeChangedCallback = function(name, oldValue, newValue) {
+ ok(false, "Only callbacks at registration should be called.");
+ };
+
+ var elem = document.createElement("x-test-callback");
+ elem.setAttribute("foo", "bar");
+}
+
+function testAttributeChanged() {
+ var createdCallbackCalled = false;
+
+ var createdElement;
+ var createdCallbackThis;
+
+ var p = Object.create(HTMLElement.prototype);
+ p.createdCallback = function() {
+ is(createdCallbackCalled, false, "Created callback should only be called once in this test.");
+ createdCallbackThis = this;
+ createdCallbackCalled = true;
+ };
+
+ // 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.
+ ];
+
+ p.attributeChangedCallback = function(name, oldValue, newValue) {
+ is(createdCallbackCalled, true, "created callback should be called before attribute changed.");
+ is(this, createdElement, "The 'this' value should be the custom element.");
+ ok(expectedCallbackArguments.length > 0, "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();
+ }
+ };
+
+ document.registerElement("x-attrchange", { prototype: p });
+
+ var createdElement = document.createElement("x-attrchange");
+ is(createdCallbackThis, createdElement, "The 'this' value in the created callback should be the custom element.");
+ createdElement.setAttribute("changeme", "newvalue");
+ createdElement.setAttribute("changeme", "nextvalue");
+ createdElement.setAttribute("changeme", "");
+ createdElement.removeAttribute("changeme");
+}
+
+function testAttributeChangedExtended() {
+ var p = Object.create(HTMLButtonElement.prototype);
+ var callbackCalled = false;
+ p.attributeChangedCallback = function(name, oldValue, newValue) {
+ is(callbackCalled, false, "Callback should only be called once in this test.");
+ callbackCalled = true;
+ runNextTest();
+ };
+
+ document.registerElement("x-extended-attribute-change", { prototype: p, extends: "button" });
+
+ var elem = document.createElement("button", {is: "x-extended-attribute-change"});
+ elem.setAttribute("foo", "bar");
+}
+
+// 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() {
+ var p = Object.create(HTMLElement.prototype);
+
+ p.attached = function() {
+ ok(false, "attached should not be called when not entering the document.");
+ };
+
+ p.detached = function() {
+ ok(false, "leaveView should not be called when not leaving the document.");
+ };
+
+ var createdElement = document.createElement("x-destined-for-fragment");
+
+ document.registerElement("x-destined-for-fragment", { prototype: p });
+
+ 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 attachedCallbackCalled = false;
+ var detachedCallbackCalled = false;
+
+ var p = Object.create(HTMLElement.prototype);
+ p.attachedCallback = function() {
+ is(attachedCallbackCalled, false, "attached callback should only be called on in this test.");
+ attachedCallbackCalled = true;
+ };
+
+ p.detachedCallback = function() {
+ is(attachedCallbackCalled, true, "attached callback should be called before detached");
+ is(detachedCallbackCalled, false, "detached callback should only be called once in this test.");
+ detachedCallbackCalled = true;
+ runNextTest();
+ };
+
+ var div = document.createElement("div");
+ document.registerElement("x-element-in-div", { prototype: p });
+ var customElement = document.createElement("x-element-in-div");
+ div.appendChild(customElement);
+ is(attachedCallbackCalled, false, "Appending a custom element to a node that is not in the document should not call the attached callback.");
+
+ container.appendChild(div);
+ container.removeChild(div);
+}
+
+var testFunctions = [
+ testRegisterUnresolved,
+ testRegisterUnresolvedExtended,
+ testInnerHTML,
+ testInnerHTMLExtended,
+ testInnerHTMLUpgrade,
+ testInnerHTMLExtendedUpgrade,
+ testRegisterResolved,
+ testAttributeChanged,
+ testAttributeChangedExtended,
+ testUpgradeCandidate,
+ testChangingCallback,
+ testNotInDocEnterLeave,
+ testEnterLeaveView,
+ SimpleTest.finish
+];
+
+function runNextTest() {
+ if (testFunctions.length > 0) {
+ var nextTestFunction = testFunctions.shift();
+ nextTestFunction();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+runNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_document_register_parser.html b/dom/tests/mochitest/webcomponents/test_document_register_parser.html
new file mode 100644
index 000000000..bc4cdcbac
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_register_parser.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783129
+-->
+<head>
+ <title>Test for document.registerElement for elements created by the parser</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script>
+
+var extendedButtonProto = Object.create(HTMLButtonElement.prototype);
+var buttonCallbackCalled = false;
+extendedButtonProto.createdCallback = function() {
+ is(buttonCallbackCalled, false, "created callback for x-button should only be called once.");
+ is(this.tagName, "BUTTON", "Only the <button> element should be upgraded.");
+ buttonCallbackCalled = true;
+};
+
+document.registerElement("x-button", { prototype: extendedButtonProto, extends: "button" });
+
+var divProto = Object.create(HTMLDivElement.prototype);
+var divCallbackCalled = false;
+divProto.createdCallback = function() {
+ is(divCallbackCalled, false, "created callback for x-div should only be called once.");
+ is(buttonCallbackCalled, true, "crated callback should be called for x-button before x-div.");
+ is(this.tagName, "X-DIV", "Only the <x-div> element should be upgraded.");
+ divCallbackCalled = true;
+ SimpleTest.finish();
+};
+
+document.registerElement("x-div", { prototype: divProto });
+
+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 -->
+<script>
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_document_register_stack.html b/dom/tests/mochitest/webcomponents/test_document_register_stack.html
new file mode 100644
index 000000000..34f108654
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_register_stack.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783129
+-->
+<head>
+ <title>Test for document.registerElement lifecycle callback</title>
+ <script type="text/javascript" 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");
+
+// Test changing attributes in the created callback on an element
+// created after registration.
+function testChangeAttributeInCreatedCallback() {
+ var createdCallbackCalled = false;
+ var attributeChangedCallbackCalled = false;
+
+ var p = Object.create(HTMLElement.prototype);
+ p.createdCallback = function() {
+ is(createdCallbackCalled, false, "Created callback should be called before attached callback.");
+ createdCallbackCalled = true;
+ is(attributeChangedCallbackCalled, false, "Attribute changed callback should not have been called prior to setting the attribute.");
+ this.setAttribute("foo", "bar");
+ is(attributeChangedCallbackCalled, false, "While element is being created, element should not be added to the current element callback queue.");
+ };
+
+ p.attributeChangedCallback = function(name, oldValue, newValue) {
+ is(createdCallbackCalled, true, "attributeChanged callback should be called after the created callback because it was enqueued during created callback.");
+ is(attributeChangedCallbackCalled, false, "attributeChanged callback should only be called once in this tests.");
+ is(newValue, "bar", "The new value should be 'bar'");
+ attributeChangedCallbackCalled = true;
+ runNextTest();
+ };
+
+ document.registerElement("x-one", { prototype: p });
+ document.createElement("x-one");
+}
+
+function testChangeAttributeInEnteredViewCallback() {
+ var p = Object.create(HTMLElement.prototype);
+ var attributeChangedCallbackCalled = false;
+ var attachedCallbackCalled = false;
+
+ p.attachedCallback = function() {
+ is(attachedCallbackCalled, false, "attached callback should be called only once in this test.");
+ attachedCallbackCalled = 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();
+ };
+
+ p.attributeChangedCallback = function() {
+ is(attachedCallbackCalled, true, "attached callback should have been called prior to attribute changed callback.");
+ is(attributeChangedCallbackCalled, false, "attributeChanged callback should only be called once in this tests.");
+ attributeChangedCallbackCalled = true;
+ };
+
+ document.registerElement("x-two", { prototype: p });
+ var elem = document.createElement("x-two");
+
+ var container = document.getElementById("container");
+ container.appendChild(elem);
+}
+
+function testLeaveViewInEnteredViewCallback() {
+ var p = Object.create(HTMLElement.prototype);
+ var attachedCallbackCalled = false;
+ var detachedCallbackCalled = false;
+ var container = document.getElementById("container");
+
+ p.attachedCallback = function() {
+ is(this.parentNode, container, "Parent node should the container in which the node was appended.");
+ is(attachedCallbackCalled, false, "attached callback should be called only once in this test.");
+ attachedCallbackCalled = true;
+ is(detachedCallbackCalled, false, "detached callback should not be called prior to removing element from document.");
+ container.removeChild(this);
+ is(detachedCallbackCalled, true, "Transition from user-agent implementation to script should run left view callback.");
+ runNextTest();
+ };
+
+ p.detachedCallback = function() {
+ is(detachedCallbackCalled, false, "The detached callback should only be called once in this test.");
+ is(attachedCallbackCalled, true, "The attached callback should be called prior to detached callback.");
+ detachedCallbackCalled = true;
+ };
+
+ document.registerElement("x-three", { prototype: p });
+ var elem = document.createElement("x-three");
+
+ container.appendChild(elem);
+}
+
+function testStackedAttributeChangedCallback() {
+ var p = Object.create(HTMLElement.prototype);
+ var attributeChangedCallbackCount = 0;
+
+ var attributeSequence = ["foo", "bar", "baz"];
+
+ p.attributeChangedCallback = function(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();
+ }
+ };
+
+ document.registerElement("x-four", { prototype: p });
+ var elem = document.createElement("x-four");
+ elem.setAttribute("foo", "changeme");
+}
+
+var testFunctions = [
+ testChangeAttributeInCreatedCallback,
+ testChangeAttributeInEnteredViewCallback,
+ testLeaveViewInEnteredViewCallback,
+ testStackedAttributeChangedCallback,
+ SimpleTest.finish
+];
+
+function runNextTest() {
+ if (testFunctions.length > 0) {
+ var nextTestFunction = testFunctions.shift();
+ nextTestFunction();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+runNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_document_shared_registry.html b/dom/tests/mochitest/webcomponents/test_document_shared_registry.html
new file mode 100644
index 000000000..76c2ea8ec
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_shared_registry.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783129
+-->
+<head>
+ <title>Test shared registry for associated HTML documents.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="container"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a>
+<script>
+var container = document.getElementById("container");
+
+function createdCallbackFromMainDoc() {
+ var createdCallbackCalled = false;
+ var assocDoc = document.implementation.createHTMLDocument();
+
+ var proto = Object.create(HTMLElement.prototype);
+ proto.createdCallback = function() {
+ is(createdCallbackCalled, false, "created callback should only be called once in this tests.");
+ createdCallbackCalled = true;
+ runNextTest();
+ };
+
+ assocDoc.registerElement("x-associated-doc-callback-elem", { prototype: proto });
+ document.createElement("x-associated-doc-callback-elem");
+}
+
+function createdCallbackFromDocHTMLNamespace() {
+ var createdCallbackCalled = false;
+ var assocDoc = document.implementation.createDocument("http://www.w3.org/1999/xhtml", "html", null);
+ var somediv = assocDoc.createElement("div");
+
+ var proto = Object.create(HTMLElement.prototype);
+ proto.createdCallback = function() {
+ is(createdCallbackCalled, false, "created callback should only be called once in this tests.");
+ createdCallbackCalled = true;
+ runNextTest();
+ };
+
+ assocDoc.registerElement("x-assoc-doc-with-ns-callback-elem", { prototype: proto });
+ document.createElement("x-assoc-doc-with-ns-callback-elem");
+}
+
+function registerNoRegistryDoc() {
+ var assocDoc = document.implementation.createDocument(null, "html");
+ try {
+ assocDoc.registerElement("x-dummy", { prototype: Object.create(HTMLElement.prototype) });
+ ok(false, "Registring element in document without registry should throw.");
+ } catch (ex) {
+ ok(true, "Registring element in document without registry should throw.");
+ }
+
+ runNextTest();
+}
+
+function runNextTest() {
+ if (testFunctions.length > 0) {
+ var nextTestFunction = testFunctions.shift();
+ nextTestFunction();
+ }
+}
+
+var testFunctions = [
+ createdCallbackFromMainDoc,
+ createdCallbackFromDocHTMLNamespace,
+ registerNoRegistryDoc,
+ SimpleTest.finish
+];
+
+SimpleTest.waitForExplicitFinish();
+
+runNextTest();
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_dynamic_content_element_matching.html b/dom/tests/mochitest/webcomponents/test_dynamic_content_element_matching.html
new file mode 100644
index 000000000..c9af76610
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_dynamic_content_element_matching.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=806506
+-->
+<head>
+ <title>Test for dynamic changes to content matching content elements</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div class="tall" id="bodydiv"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
+<script>
+// Create ShadowRoot.
+var elem = document.createElement("div");
+var root = elem.createShadowRoot();
+
+var redInsertionPoint = document.createElement("content");
+redInsertionPoint.select = "*[data-color=red]";
+
+var blueInsertionPoint = document.createElement("content");
+blueInsertionPoint.select = "*[data-color=blue]";
+
+root.appendChild(redInsertionPoint);
+root.appendChild(blueInsertionPoint);
+
+is(blueInsertionPoint.getDistributedNodes().length, 0, "Blue insertion point should have no distributed nodes.");
+is(redInsertionPoint.getDistributedNodes().length, 0, "Red insertion point should have no distrubted nodes.");
+
+var matchElement = document.createElement("div");
+matchElement.setAttribute("data-color", "red");
+elem.appendChild(matchElement);
+
+is(blueInsertionPoint.getDistributedNodes().length, 0, "Blue insertion point should have no distributed nodes.");
+is(redInsertionPoint.getDistributedNodes().length, 1, "Red insertion point should match recently inserted div.");
+
+matchElement.setAttribute("data-color", "blue");
+is(blueInsertionPoint.getDistributedNodes().length, 1, "Blue insertion point should match element after changing attribute value.");
+is(redInsertionPoint.getDistributedNodes().length, 0, "Red insertion point should not match element after changing attribute value.");
+
+matchElement.removeAttribute("data-color");
+
+is(blueInsertionPoint.getDistributedNodes().length, 0, "Blue insertion point should have no distributed nodes after removing the matching attribute.");
+is(redInsertionPoint.getDistributedNodes().length, 0, "Red insertion point should have no distrubted nodes after removing the matching attribute.");
+
+</script>
+</body>
+</html>
+
diff --git a/dom/tests/mochitest/webcomponents/test_event_dispatch.html b/dom/tests/mochitest/webcomponents/test_event_dispatch.html
new file mode 100644
index 000000000..c73bfb214
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_event_dispatch.html
@@ -0,0 +1,458 @@
+<!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="/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);
+
+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);
+ }
+
+ // Check to make sure the event chain matches what we get back from nsIEventListenerService.getEventTargetChainFor
+ if (0 < actual.length) {
+ var chain = els.getEventTargetChainFor(actual[0], true); // Events should be dispatched on actual[0].
+ for (var i = 0; i < expected.length; i++) {
+ ok(SpecialPowers.compare(chain[i], expected[i]), msg + " at " + i + " for nsIEventListenerService");
+ }
+ }
+}
+
+/*
+ * Test 1: Test of event dispatch through a basic ShadowRoot with content a insertion point.
+ *
+ * <div elemOne> ------ <shadow-root shadowOne>
+ * | |
+ * <div elemTwo> <span elemThree>
+ * |
+ * <content elemFour>
+ */
+
+var elemOne = document.createElement("div");
+elemOne.addEventListener("custom", eventListener);
+
+var elemTwo = document.createElement("div");
+elemTwo.addEventListener("custom", eventListener);
+
+var elemThree = document.createElement("span");
+elemThree.addEventListener("custom", eventListener);
+
+var elemFour = document.createElement("content");
+elemFour.addEventListener("custom", eventListener);
+
+var shadowOne = elemOne.createShadowRoot();
+shadowOne.addEventListener("custom", eventListener);
+
+elemThree.appendChild(elemFour);
+shadowOne.appendChild(elemThree);
+elemOne.appendChild(elemTwo);
+
+var eventChain = [];
+var customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemTwo.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemTwo, elemFour, elemThree, shadowOne, elemOne], "Event path for test 1 for event dispatched on elemTwo.");
+
+/*
+ * Test 2: Test of event dispatch through a nested ShadowRoots with content insertion points.
+ *
+ * <div elemFive> --- <shadow-root shadowTwo>
+ * | |
+ * <div elemOne> <div elemFour> ----- <shadow-root shadowOne>
+ * | |
+ * <content elemTwo> <p elemSix>
+ * |
+ * <content elemThree>
+ */
+
+elemOne = document.createElement("div");
+elemOne.addEventListener("custom", eventListener);
+
+elemTwo = document.createElement("content");
+elemTwo.addEventListener("custom", eventListener);
+
+elemThree = document.createElement("content");
+elemThree.addEventListener("custom", eventListener);
+
+var elemFour = document.createElement("div");
+elemFour.addEventListener("custom", eventListener);
+
+var elemFive = document.createElement("div");
+elemFive.addEventListener("custom", eventListener);
+
+var elemSix = document.createElement("p");
+elemSix.addEventListener("custom", eventListener);
+
+var shadowOne = elemFour.createShadowRoot();
+shadowOne.addEventListener("custom", eventListener);
+
+var shadowTwo = elemFive.createShadowRoot();
+shadowTwo.addEventListener("custom", eventListener);
+
+elemFive.appendChild(elemOne);
+shadowTwo.appendChild(elemFour);
+elemFour.appendChild(elemTwo);
+shadowOne.appendChild(elemSix);
+elemSix.appendChild(elemThree);
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemOne.dispatchEvent(customEvent);
+is(elemOne.getDestinationInsertionPoints().length, 2, "yes");
+isEventChain(eventChain, [elemOne, elemThree, elemSix, shadowOne, elemTwo, elemFour, shadowTwo, elemFive], "Event path for test 2 for event dispatched on elemOne.");
+
+/*
+ * Test 3: Test of event dispatch through nested ShadowRoot with content insertion points.
+ *
+ * <div elemOne> ------- <shadow-root shadowOne>
+ * | |
+ * <span elemTwo> <span elemThree> ------------ <shadow-root shadowTwo>
+ * | |
+ * <span elemFour> <content elemSix>
+ * |
+ * <content elemFive>
+ */
+
+elemOne = document.createElement("div");
+elemOne.addEventListener("custom", eventListener);
+
+elemTwo = document.createElement("span");
+elemTwo.addEventListener("custom", eventListener);
+
+elemThree = document.createElement("span");
+elemThree.addEventListener("custom", eventListener);
+
+elemFour = document.createElement("span");
+elemFour.addEventListener("custom", eventListener);
+
+elemFive = document.createElement("content");
+elemFive.addEventListener("custom", eventListener);
+
+elemSix = document.createElement("content");
+elemSix.addEventListener("custom", eventListener);
+
+shadowOne = elemOne.createShadowRoot();
+shadowOne.addEventListener("custom", eventListener);
+
+shadowTwo = elemThree.createShadowRoot();
+shadowTwo.addEventListener("custom", eventListener);
+
+elemOne.appendChild(elemTwo);
+shadowOne.appendChild(elemThree);
+elemThree.appendChild(elemFour);
+elemFour.appendChild(elemFive);
+shadowTwo.appendChild(elemSix);
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemTwo.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemTwo, elemFive, elemFour, elemSix, shadowTwo, elemThree, shadowOne, elemOne], "Event path for test 3 for event dispatched on elemTwo.");
+
+/*
+ * Test 4: Test of event dispatch through host with multiple ShadowRoots with shadow insertion point.
+ *
+ * <div elemSeven> --- <shadow-root shadowTwo> (younger ShadowRoot)
+ * | | |
+ * <div elemOne> | <div elemSix> -------- <shadow-root shadowOne>
+ * | | |
+ * | <shadow elemFour> <content elemFive>
+ * | |
+ * | <content elemTwo>
+ * |
+ * --- <shadow-root shadowThree> (older ShadowRoot)
+ * | |
+ * | <content elemThree>
+ * |
+ * <div elemEight>
+ */
+
+elemOne = document.createElement("div");
+elemOne.addEventListener("custom", eventListener);
+
+elemTwo = document.createElement("content");
+elemTwo.addEventListener("custom", eventListener);
+
+elemThree = document.createElement("content");
+elemThree.addEventListener("custom", eventListener);
+
+elemFour = document.createElement("shadow");
+elemFour.addEventListener("custom", eventListener);
+
+elemFive = document.createElement("content");
+elemFive.addEventListener("custom", eventListener);
+
+elemSix = document.createElement("div");
+elemSix.addEventListener("custom", eventListener);
+
+var elemSeven = document.createElement("div");
+elemSeven.addEventListener("custom", eventListener);
+
+var elemEight = document.createElement("div");
+elemEight.addEventListener("custom", eventListener);
+
+var shadowThree = elemSeven.createShadowRoot();
+shadowThree.addEventListener("custom", eventListener);
+
+shadowTwo = elemSeven.createShadowRoot();
+shadowTwo.addEventListener("custom", eventListener);
+
+shadowOne = elemSix.createShadowRoot();
+shadowOne.addEventListener("custom", eventListener);
+
+elemSeven.appendChild(elemOne);
+shadowTwo.appendChild(elemSix);
+elemSix.appendChild(elemFour);
+elemFour.appendChild(elemTwo);
+shadowThree.appendChild(elemEight);
+shadowThree.appendChild(elemThree);
+shadowOne.appendChild(elemFive);
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemOne.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemOne, elemFive, shadowOne, elemThree, shadowThree, elemTwo, elemFour, elemSix, shadowTwo, elemSeven], "Event path for test 4 for event dispatched on elemOne.");
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemEight.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemEight, elemFive, shadowOne, elemSix, shadowTwo, elemSeven], "Event path for test 4 for event dispatched on elemEight.");
+
+/*
+ * Test 5: Test of event dispatch through nested shadowroot with insertion points that match specific tags.
+ *
+ * <div elemOne> --------- <shadow-root shadowOne>
+ * | | |
+ * | <p elemThree> <span elemFour> ------------------------ <shadow-root shadowTwo>
+ * | | | |
+ * <span elemTwo> | <content select="p" elemFive> <content elemSeven>
+ * |
+ * <content select="span" elemSix>
+ */
+
+elemOne = document.createElement("div");
+elemOne.addEventListener("custom", eventListener);
+
+elemTwo = document.createElement("span");
+elemTwo.addEventListener("custom", eventListener);
+
+elemThree = document.createElement("p");
+elemThree.addEventListener("custom", eventListener);
+
+elemFour = document.createElement("span");
+elemFour.addEventListener("custom", eventListener);
+
+elemFive = document.createElement("content");
+elemFive.select = "p";
+elemFive.addEventListener("custom", eventListener);
+
+elemSix = document.createElement("content");
+elemSix.select = "span";
+elemSix.addEventListener("custom", eventListener);
+
+elemSeven = document.createElement("content");
+elemSeven.addEventListener("custom", eventListener);
+
+shadowTwo = elemFour.createShadowRoot();
+shadowTwo.addEventListener("custom", eventListener);
+
+shadowOne = elemOne.createShadowRoot();
+shadowOne.addEventListener("custom", eventListener);
+
+elemOne.appendChild(elemTwo);
+elemOne.appendChild(elemThree);
+shadowOne.appendChild(elemFour);
+elemFour.appendChild(elemSix);
+elemFour.appendChild(elemFive);
+shadowTwo.appendChild(elemSeven);
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemTwo.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemTwo, elemSeven, shadowTwo, elemSix, elemFour, shadowOne, elemOne], "Event path for test 5 for event dispatched on elemTwo.");
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemThree.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemThree, elemSeven, shadowTwo, elemFive, elemFour, shadowOne, elemOne], "Event path for test 5 for event dispatched on elemThree.");
+
+/*
+ * Test 6: Test of event dispatch through nested shadowroot with insertion points that match specific tags.
+ *
+ * <div elemOne> --------- <shadow-root shadowOne>;
+ * | | |
+ * | <p elemThree> <span elemFour> ------ <shadow-root shadowTwo>
+ * | | | |
+ * <span elemTwo> <content elemFive> | <content select="p" elemSeven>
+ * |
+ * <content select="span" elemSix>
+ */
+
+elemOne = document.createElement("div");
+elemOne.addEventListener("custom", eventListener);
+
+elemTwo = document.createElement("span");
+elemTwo.addEventListener("custom", eventListener);
+
+elemThree = document.createElement("p");
+elemThree.addEventListener("custom", eventListener);
+
+elemFour = document.createElement("span");
+elemFour.addEventListener("custom", eventListener);
+
+elemFive = document.createElement("content");
+elemFive.addEventListener("custom", eventListener);
+
+elemSix = document.createElement("content");
+elemSix.select = "span";
+elemSix.addEventListener("custom", eventListener);
+
+elemSeven = document.createElement("content");
+elemSeven.select = "p";
+elemSeven.addEventListener("custom", eventListener);
+
+shadowTwo = elemFour.createShadowRoot();
+shadowTwo.addEventListener("custom", eventListener);
+
+shadowOne = elemOne.createShadowRoot();
+shadowOne.addEventListener("custom", eventListener);
+
+elemOne.appendChild(elemTwo);
+elemOne.appendChild(elemThree);
+shadowOne.appendChild(elemFour);
+elemFour.appendChild(elemFive);
+shadowTwo.appendChild(elemSix);
+shadowTwo.appendChild(elemSeven);
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemTwo.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemTwo, elemSix, shadowTwo, elemFive, elemFour, shadowOne, elemOne], "Event path for test 6 for event dispatched on elemTwo.");
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemThree.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemThree, elemSeven, shadowTwo, elemFive, elemFour, shadowOne, elemOne], "Event path for test 6 for event dispatched on elemThree.");
+
+/*
+ * Test 7: Test of event dispatch through nested shadowroot with insertion points that match specific tags.
+ *
+ * <div elemOne> --------- <shadow-root shadowOne>
+ * | | |
+ * | <p elemThree> <span elemFour> ------ <shadow-root shadowTwo>
+ * | | |
+ * <span elemTwo> <content elemFive> <span elemEight>
+ * | |
+ * | <content select="p" elemSeven>
+ * |
+ * <content select="span" elemSix>
+ */
+
+elemOne = document.createElement("div");
+elemOne.addEventListener("custom", eventListener);
+
+elemTwo = document.createElement("span");
+elemTwo.addEventListener("custom", eventListener);
+
+elemThree = document.createElement("p");
+elemThree.addEventListener("custom", eventListener);
+
+elemFour = document.createElement("span");
+elemFour.addEventListener("custom", eventListener);
+
+elemFive = document.createElement("content");
+elemFive.addEventListener("custom", eventListener);
+
+elemSix = document.createElement("content");
+elemSix.select = "span";
+elemSix.addEventListener("custom", eventListener);
+
+elemSeven = document.createElement("content");
+elemSeven.select = "p";
+elemSeven.addEventListener("custom", eventListener);
+
+elemEight = document.createElement("span");
+elemEight.addEventListener("custom", eventListener);
+
+shadowTwo = elemFour.createShadowRoot();
+shadowTwo.addEventListener("custom", eventListener);
+
+shadowOne = elemOne.createShadowRoot();
+shadowOne.addEventListener("custom", eventListener);
+
+elemOne.appendChild(elemTwo);
+elemOne.appendChild(elemThree);
+shadowOne.appendChild(elemFour);
+elemFour.appendChild(elemFive);
+shadowTwo.appendChild(elemEight);
+elemEight.appendChild(elemSix);
+elemEight.appendChild(elemSeven);
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemTwo.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemTwo, elemSix, elemEight, shadowTwo, elemFive, elemFour, shadowOne, elemOne], "Event path for test 7 for event dispatched on elemTwo.");
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemThree.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemThree, elemSeven, elemEight, shadowTwo, elemFive, elemFour, shadowOne, elemOne], "Event path for test 7 for event dispatched on elemThree.");
+
+/*
+ * Test 8: Test of event dispatch through host with multiple ShadowRoots with shadow insertion point.
+ *
+ * <div elemOne> --- <shadow-root shadowOne> (younger ShadowRoot)
+ * | |
+ * | <div elemFour>
+ * | |
+ * | <shadow elemTwo>
+ * |
+ * --- <shadow-root shadowTwo> (older ShadowRoot)
+ * |
+ * <div elemThree>
+ */
+
+elemOne = document.createElement("div");
+elemOne.addEventListener("custom", eventListener);
+
+elemTwo = document.createElement("shadow");
+elemTwo.addEventListener("custom", eventListener);
+
+elemThree = document.createElement("div");
+elemThree.addEventListener("custom", eventListener);
+
+elemFour = document.createElement("div");
+elemFour.addEventListener("custom", eventListener);
+
+shadowTwo = elemOne.createShadowRoot();
+shadowTwo.addEventListener("custom", eventListener);
+
+shadowOne = elemOne.createShadowRoot();
+shadowOne.addEventListener("custom", eventListener);
+
+shadowOne.appendChild(elemFour);
+elemFour.appendChild(elemTwo);
+shadowTwo.appendChild(elemThree);
+
+eventChain = [];
+customEvent = new CustomEvent("custom", { "bubbles" : true, "composed" : true });
+elemThree.dispatchEvent(customEvent);
+isEventChain(eventChain, [elemThree, shadowTwo, elemTwo, elemFour, shadowOne, elemOne], "Event path for test 8 for event dispatched on elemThree.");
+
+</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 000000000..ed81faf48
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_event_retarget.html
@@ -0,0 +1,147 @@
+<!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="/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>
+
+/*
+ * 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 = document.createElement("div");
+var elemTwo = document.createElement("content");
+var elemThree = document.createElement("div");
+var shadowOne = elemThree.createShadowRoot();
+
+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 = document.createElement("div");
+elemTwo = document.createElement("content");
+elemThree = document.createElement("div");
+shadowOne = elemThree.createShadowRoot();
+
+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 = document.createElement("div");
+elemTwo = document.createElement("content");
+elemThree = document.createElement("content");
+var elemFour = document.createElement("div");
+var elemFive = document.createElement("div");
+var shadowTwo = elemFive.createShadowRoot();
+shadowOne = elemFour.createShadowRoot();
+
+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 = document.createElement("div");
+elemTwo = document.createElement("content");
+elemThree = document.createElement("content");
+elemFour = document.createElement("div");
+elemFive = document.createElement("div");
+shadowTwo = elemFive.createShadowRoot();
+shadowOne = elemFour.createShadowRoot();
+
+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);
+
+</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 000000000..970e44039
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_event_stopping.html
@@ -0,0 +1,168 @@
+<!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="/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);
+
+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 (0 < 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 = document.createElement("div");
+var elemTwo = document.createElement("span");
+var elemThree = document.createElement("span");
+var shadowOne = elemOne.createShadowRoot();
+
+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.");
+
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_fallback_dest_insertion_points.html b/dom/tests/mochitest/webcomponents/test_fallback_dest_insertion_points.html
new file mode 100644
index 000000000..4eefa165f
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_fallback_dest_insertion_points.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=999999
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 999999</title>
+ <script type="application/javascript" 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=999999">Mozilla Bug 999999</a>
+<p id="display"></p>
+<div id="content">
+<div id="shadowhost"></div>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+
+/** Test for Bug 999999 **/
+var host = document.getElementById("shadowhost");
+
+// Test destination insertion points of node distributed to content element.
+var shadowRoot = host.createShadowRoot();
+shadowRoot.innerHTML = '<div id="innerhost"><content id="innercontent"></content></div>';
+
+var fallback = document.createElement("span");
+var innerContent = shadowRoot.getElementById("innercontent");
+
+innerContent.appendChild(fallback);
+
+is(fallback.getDestinationInsertionPoints().length, 1, "Active fallback content should be distributed to insertion point.");
+is(fallback.getDestinationInsertionPoints()[0], innerContent, "Insertion point should be in list of destination insertion points.");
+
+// Test destination insertion points of reprojected fallback content.
+var innerHost = shadowRoot.getElementById("innerhost");
+var innerShadowRoot = innerHost.createShadowRoot();
+innerShadowRoot.innerHTML = '<content id="innerinnercontent"></content>';
+
+var innerInnerContent = innerShadowRoot.getElementById("innerinnercontent");
+
+is(fallback.getDestinationInsertionPoints().length, 2, "Fallback content should have been distributed into parent and reprojected into another insertion point.");
+is(fallback.getDestinationInsertionPoints()[1], innerInnerContent, "Destination insertion points should contain content element to which the node was reprojected.");
+
+// Test destination insertion points of fallback content that was dropped due to content element matching a node in the host.
+var span = document.createElement("span");
+host.appendChild(span);
+
+is(fallback.getDestinationInsertionPoints().length, 0, "After dropping insertion points, fallback content should not have any nodes in destination insertion points list.");
+
+// Test destination insertion points of fallback content after reactivating by dropping matched content on host.
+host.removeChild(span);
+is(fallback.getDestinationInsertionPoints().length, 2, "Fallback content should have 2 destination insertion points after being reactivated.");
+is(fallback.getDestinationInsertionPoints()[0], innerContent, "First destination insertion point should be the parent content");
+is(fallback.getDestinationInsertionPoints()[1], innerInnerContent, "Second destination insertion point should be the content to which the node is reprojected.");
+
+// Test destination insertion points of fallback content after removed from the tree.
+innerContent.removeChild(fallback);
+is(fallback.getDestinationInsertionPoints().length, 0, "Fallback content is no longer fallback content, destination insertion points should be empty.");
+
+// Test destination insertion points of child of non-insertion point content element.
+var notInsertionPointContent = document.createElement("content");
+var notFallback = document.createElement("span");
+notInsertionPointContent.appendChild(notFallback);
+is(notFallback.getDestinationInsertionPoints().length, 0, "Child of non-insertion point content should not be distributed to any nodes.");
+
+</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 000000000..824b9e5ff
--- /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 type="text/javascript" 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", null, null);
+ prefetch.cancelPrefetchURI(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", null, null);
+ prefetch.cancelPrefetchURI(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_nested_content_element.html b/dom/tests/mochitest/webcomponents/test_nested_content_element.html
new file mode 100644
index 000000000..1d98d2996
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_nested_content_element.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=806506
+-->
+<head>
+ <title>Test for HTMLContent element</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="grabme"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
+<script>
+
+/**
+ * Constructs a node with a nested ShadowRoot with the following structure:
+ * <span> - - - - - - - - - - <ShadowRoot>
+ * <span> <span> - - - - - - - - - - <ShadowRoot>
+ * id=one id=four <span>
+ * data-color=red data-color=orange id=eleven
+ * <span> <span> <content>
+ * id=two id=five id=twelve
+ * data-color=blue data-color=purple select=secondSelect
+ * <span> <content> <span>
+ * id=three id=six id=thirteen
+ * data-color=green select=firstSelect
+ * <span>
+ * id=seven
+ * <content>
+ * id=eight
+ * <span>
+ * id=nine
+ * <span>
+ * id=ten
+ * data-color=grey
+ */
+function constructTree(firstSelect, secondSelect) {
+ var rootSpan = document.createElement("span");
+ rootSpan.innerHTML = '<span id="one" data-color="red"></span><span id="two" data-color="blue"></span><span id="three" data-color="green"></span>';
+ var firstShadow = rootSpan.createShadowRoot();
+ firstShadow.innerHTML = '<span id="four" data-color="orange"><span id="five" data-color="purple"></span><content id="six" select="' + firstSelect + '"><span id="seven"></span><content id="eight"></content><span id="nine"></span></content><span id="ten"></span></span>';
+ var secondShadow = firstShadow.firstChild.createShadowRoot();
+ secondShadow.innerHTML = '<span id="eleven"></span><content id="twelve" select="' + secondSelect + '"></content><span id="thirteen"></span>';
+ return rootSpan;
+}
+
+// Create a tree with content that matches on everything and check node distribution.
+var allSpan = constructTree("*", "*");
+var firstContent = allSpan.shadowRoot.getElementById("six");
+var firstDistNodes = firstContent.getDistributedNodes();
+is(firstDistNodes.length, 3, "Universal selector should match all nodes.");
+// Check the order of the distributed nodes.
+is(firstDistNodes.item(0).id, "one", "First distributed node should have id of 'one'");
+is(firstDistNodes.item(1).id, "two", "Second distributed node should have id of 'two'");
+is(firstDistNodes.item(2).id, "three", "Third distributed node should have id of 'three'");
+var secondContent = allSpan.shadowRoot.firstChild.shadowRoot.getElementById("twelve");
+var secondDistNodes = secondContent.getDistributedNodes();
+is(secondDistNodes.length, 5, "Universial selector should match all nodes including those distributed into content.");
+// Check the order of the distribute nodes.
+is(secondDistNodes.item(0).id, "five", "First distributed node should have id of 'five'");
+is(secondDistNodes.item(1).id, "one", "Second distributed (reprojected) node should have id of 'one'");
+is(secondDistNodes.item(2).id, "two", "Third distributed (reprojected) node should have id of 'two'");
+is(secondDistNodes.item(3).id, "three", "Fourth distributed (reprojected) node should have id of 'three'");
+is(secondDistNodes.item(4).id, "ten", "Fifth distributed node should have id of 'ten'");
+
+// Append an element after id=two and make sure that it is inserted into the corrent
+// position in the insertion points.
+var additionalSpan = document.createElement("span");
+additionalSpan.id = "additional";
+
+// Insert the additional span in the third position, before the span with id=three.
+allSpan.insertBefore(additionalSpan, allSpan.childNodes.item(2));
+firstDistNodes = firstContent.getDistributedNodes();
+secondDistNodes = secondContent.getDistributedNodes();
+is(firstDistNodes.length, 4, "First insertion point should match one more node.");
+is(firstDistNodes.item(2).id, "additional", "Additional span should have been inserted into the third position of the first insertion point.");
+
+is(secondDistNodes.length, 6, "Second insertion point should match one more node.");
+is(secondDistNodes.item(3).id, "additional", "Additional span should have been inserted into the fourth position of the second insertion point.");
+
+function nodeListDoesNotContain(nodeList, element) {
+ for (var i = 0; i < nodeList.length; i++) {
+ if (nodeList[i] == element) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Remove the span with id=one and check that it is removed from all insertion points.
+allSpan = constructTree("*", "*");
+var spanOne = allSpan.firstChild;
+allSpan.removeChild(spanOne);
+firstContent = allSpan.shadowRoot.getElementById("six");
+ok(nodeListDoesNotContain(firstContent.getDistributedNodes(), spanOne), "Child removed from host should not appear in insertion point node list.");
+secondContent = allSpan.shadowRoot.firstChild.shadowRoot.getElementById("twelve");
+ok(nodeListDoesNotContain(secondContent.getDistributedNodes(), spanOne), "Child removed from host should not appear in nested insertion point node list.");
+
+// Make sure <content> in fallback content is inactive.
+// First insertion point will not match anything and will use fallback content.
+allSpan = constructTree("#nomatch", "*");
+var fallbackInsertionPoint = allSpan.shadowRoot.getElementById("eight");
+is(fallbackInsertionPoint.getDistributedNodes().length, 0, "Insertion points in default content should be inactive.");
+
+// Insertion points with non-universal selectors.
+allSpan = constructTree("span[data-color=blue]", "*");
+firstContent = allSpan.shadowRoot.getElementById("six");
+is(firstContent.getDistributedNodes().length, 1, "Insertion point selector should only match one node.");
+is(firstContent.getDistributedNodes()[0].dataset.color, "blue", "Projected node should match selector.");
+secondContent = allSpan.shadowRoot.firstChild.shadowRoot.getElementById("twelve");
+is(secondContent.getDistributedNodes().length, 3, "Second insertion point should match two children and one reprojected node.");
+is(secondContent.getDistributedNodes()[1].dataset.color, "blue", "Projected node should match selector.");
+
+allSpan = constructTree("span[data-color=blue]", "span[data-color=blue]");
+firstContent = allSpan.shadowRoot.getElementById("six");
+is(firstContent.getDistributedNodes().length, 1, "Insertion point selector should only match one node.");
+is(firstContent.getDistributedNodes()[0].dataset.color, "blue", "Projected node should match selector.");
+secondContent = allSpan.shadowRoot.firstChild.shadowRoot.getElementById("twelve");
+is(secondContent.getDistributedNodes().length, 1, "Insertion point should only match reprojected node.");
+is(secondContent.getDistributedNodes()[0].dataset.color, "blue", "Projected node should match selector.");
+
+// Make sure that dynamically appended default content will get distributed.
+</script>
+</body>
+</html>
+
diff --git a/dom/tests/mochitest/webcomponents/test_shadow_element.html b/dom/tests/mochitest/webcomponents/test_shadow_element.html
new file mode 100644
index 000000000..4440650d7
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_shadow_element.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=887538
+-->
+<head>
+ <title>Test for HTMLShadowElement</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="grabme"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887538">Bug 887538</a>
+<script>
+var host = document.createElement("span");
+
+// Create three shadow roots on a single host and make sure that shadow elements
+// are associated with the correct shadow root.
+var firstShadow = host.createShadowRoot();
+firstShadow.innerHTML = '<shadow id="shadowone"></shadow>';
+var secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<shadow id="shadowtwo"></shadow>';
+var thirdShadow = host.createShadowRoot();
+thirdShadow.innerHTML = '<shadow id="shadowthree"></shadow>';
+
+is(firstShadow.getElementById("shadowone").olderShadowRoot, null, "Shadow element in oldest ShadowRoot should not be associated with a ShadowRoot.");
+is(secondShadow.getElementById("shadowtwo").olderShadowRoot, firstShadow, "Shadow element should be associated with older ShadowRoot.");
+is(thirdShadow.getElementById("shadowthree").olderShadowRoot, secondShadow, "Shadow element should be associated with older ShadowRoot.");
+
+// Only the first ShadowRoot in tree order is an insertion point.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<shadow id="shadowone"></shadow><shadow id="shadowtwo"></shadow>';
+var shadowElemOne = secondShadow.getElementById("shadowone");
+var shadowElemTwo = secondShadow.getElementById("shadowtwo");
+
+is(shadowElemOne.olderShadowRoot, firstShadow, "First <shadow> in tree order should be an insertion point.");
+is(shadowElemTwo.olderShadowRoot, null, "Second <shadow> in tree order should not be an insertion point.");
+
+// Remove the first <shadow> element and make sure the second <shadow> element becomes an insertion point.
+secondShadow.removeChild(shadowElemOne);
+is(shadowElemOne.olderShadowRoot, null, "<shadow> element not in a ShadowRoot is not an insertion point.");
+is(shadowElemTwo.olderShadowRoot, firstShadow, "Second <shadow> element should become insertion point after first is removed.");
+
+// Insert a <shadow> element before the current shadow insertion point and make sure that it becomes an insertion point.
+secondShadow.insertBefore(shadowElemOne, shadowElemTwo);
+is(shadowElemOne.olderShadowRoot, firstShadow, "<shadow> element inserted as first in tree order should become an insertion point.");
+is(shadowElemTwo.olderShadowRoot, null, "<shadow> element should no longer be an insertion point it another is inserted before.");
+
+// <shadow> element in fallback content is not an insertion point.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<content><shadow id="shadowone"></shadow></content><shadow id="shadowtwo"></shadow>';
+shadowElemOne = secondShadow.getElementById("shadowone");
+shadowElemTwo = secondShadow.getElementById("shadowtwo");
+
+is(shadowElemOne.olderShadowRoot, null, "<shadow> element in fallback content is not an insertion point.");
+is(shadowElemTwo.olderShadowRoot, null, "<shadow> element preceeded by another <shadow> element is not an insertion point.");
+
+// <shadow> element that is descendant of shadow element is not an insertion point.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<shadow><shadow id="shadowone"></shadow></shadow>';
+shadowElemOne = secondShadow.getElementById("shadowone");
+is(shadowElemOne.olderShadowRoot, null, "<shadow> element that is descendant of shadow element is not an insertion point.");
+
+// Check projection of <content> elements through <shadow> elements.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+secondShadow = host.createShadowRoot();
+firstShadow.innerHTML = '<content id="firstcontent"></content>';
+secondShadow.innerHTML = '<shadow><span id="one"></span><content id="secondcontent"></content><span id="four"></span></shadow>';
+host.innerHTML = '<span id="two"></span><span id="three"></span>';
+var firstContent = firstShadow.getElementById("firstcontent");
+var secondContent = secondShadow.getElementById("secondcontent");
+var firstDistNodes = firstContent.getDistributedNodes();
+var secondDistNodes = secondContent.getDistributedNodes();
+
+is(secondDistNodes.length, 2, "There should be two distributed nodes from the host.");
+ok(secondDistNodes[0].id == "two" &&
+ secondDistNodes[1].id == "three", "Nodes projected from host should preserve order.");
+
+is(firstDistNodes.length, 4, "There should be four distributed nodes, two from the first shadow, two from the second shadow.");
+ok(firstDistNodes[0].id == "one" &&
+ firstDistNodes[1].id == "two" &&
+ firstDistNodes[2].id == "three" &&
+ firstDistNodes[3].id == "four", "Reprojection through shadow should preserve node order.");
+
+// Remove a node from the host and make sure that it is removed from all insertion points.
+host.removeChild(host.firstChild);
+firstDistNodes = firstContent.getDistributedNodes();
+secondDistNodes = secondContent.getDistributedNodes();
+
+is(secondDistNodes.length, 1, "There should be one distriubted node remaining after removing node from host.");
+ok(secondDistNodes[0].id == "three", "Span with id=two should have been removed from content element.");
+is(firstDistNodes.length, 3, "There should be three distributed nodes remaining after removing node from host.");
+ok(firstDistNodes[0].id == "one" &&
+ firstDistNodes[1].id == "three" &&
+ firstDistNodes[2].id == "four", "Reprojection through shadow should preserve node order.");
+
+// Check projection of <shadow> elements to <content> elements.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<span id="firstspan"><shadow></shadow></span>';
+thirdShadow = secondShadow.getElementById("firstspan").createShadowRoot();
+thirdShadow.innerHTML = '<content id="firstcontent"></content>';
+firstContent = thirdShadow.getElementById("firstcontent");
+var shadowChild = document.createElement("span");
+firstShadow.appendChild(shadowChild);
+
+is(firstContent.getDistributedNodes()[0], shadowChild, "Elements in shadow insertioin point should be projected into content insertion points.");
+
+// Remove child of ShadowRoot and check that projected node is removed from insertion point.
+firstShadow.removeChild(firstShadow.firstChild);
+
+is(firstContent.getDistributedNodes().length, 0, "Reprojected element was removed from ShadowRoot, thus it should be removed from content insertion point.");
+
+// Check deeply nested projection of <shadow> elements.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+firstShadow.innerHTML = '<content></content>';
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<shadow><content></content></shadow>';
+thirdShadow = host.createShadowRoot();
+thirdShadow.innerHTML = '<span id="firstspan"><shadow><content></content></shadow></span>';
+var fourthShadow = thirdShadow.getElementById("firstspan").createShadowRoot();
+fourthShadow.innerHTML = '<content id="firstcontent"></content>';
+firstContent = fourthShadow.getElementById("firstcontent");
+host.innerHTML = '<span></span>';
+
+is(firstContent.getDistributedNodes()[0], host.firstChild, "Child of host should be projected to insertion point.");
+
+// Remove node and make sure that it is removed from distributed nodes.
+host.removeChild(host.firstChild);
+
+is(firstContent.getDistributedNodes().length, 0, "Node removed from host should be removed from insertion point.");
+
+// Check projection of fallback content through <shadow> elements.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+firstShadow.innerHTML = '<content><span id="firstspan"></span></content>';
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<span id="secondspan"><shadow id="firstshadow"></shadow></span>';
+firstShadowElem = secondShadow.getElementById("firstshadow");
+thirdShadow = secondShadow.getElementById("secondspan").createShadowRoot();
+thirdShadow.innerHTML = '<content id="firstcontent"></content>';
+firstContent = thirdShadow.getElementById("firstcontent");
+
+is(firstContent.getDistributedNodes().length, 1, "There should be one node distributed from fallback content.");
+is(firstContent.getDistributedNodes()[0], firstShadow.getElementById("firstspan"), "Fallback content should be distributed.");
+
+// Add some content to the host (causing the fallback content to be dropped) and make sure distribution nodes are updated.
+
+var newElem = document.createElement("div");
+firstShadowElem.appendChild(newElem);
+
+is(firstContent.getDistributedNodes().length, 1, "There should be one node distributed from the host.");
+is(firstContent.getDistributedNodes()[0], newElem, "Distributed node should be from host, not fallback content.");
+
+// Remove the distribution node and check that fallback content is used.
+firstShadowElem.removeChild(newElem);
+
+is(firstContent.getDistributedNodes().length, 1, "There should be one node distributed from fallback content.");
+is(firstContent.getDistributedNodes()[0], firstShadow.getElementById("firstspan"), "Fallback content should be distributed after removing node from host.");
+
+</script>
+</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 000000000..9f913d007
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=806506
+-->
+<head>
+ <title>Test for ShadowRoot</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="movedtoshadow" class="testclass"></div>
+<svg id="svgmovedtoshadow"></svg>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
+<script>
+// Create ShadowRoot.
+var element = document.createElement("div");
+ok(!element.shadowRoot, "div element should not have a shadow root.");
+var shadow = element.createShadowRoot();
+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 = document.getElementById("movedtoshadow");
+var inShadowSVGEl = document.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(!document.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(!document.getElementById("svgmovedtoshadow"), "SVG element appended to a ShadowRoot should not be accessible from document.");
+
+// Test getElementsByClassName
+is(document.getElementsByClassName("testclass").length, 0, "Element removed from DOM should not be accessible by DOM accessors.");
+is(shadow.getElementsByClassName("testclass").length, 1, "Element added to ShadowRoot should be accessible by ShadowRoot API.");
+
+// Test getElementsByTagName{NS}
+is(document.getElementsByTagName("div").length, 0, "Element removed from DOM should not be accessible from DOM accessors.");
+is(shadow.getElementsByTagName("div").length, 1, "Elements in the ShadowRoot should be accessible from the ShadowRoot API.");
+is(document.getElementsByTagName("svg").length, 0, "SVG elements removed from DOM should not be accessible from DOM accessors.");
+is(shadow.getElementsByTagName("svg").length, 1, "SVG element in the ShadowRoot should be accessible from the ShadowRoot API.");
+is(shadow.getElementsByTagNameNS("http://www.w3.org/2000/svg", "svg").length, 1, "SVG element in the ShadowRoot should be accessible from the ShadowRoot API.");
+
+// 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.");
+is(shadow.getElementsByClassName("testclass").length, 0, "ShadowRoot getElementsByClassName should not be able to access elements removed from ShadowRoot.");
+is(shadow.getElementsByTagName("svg").length, 0, "ShadowRoot getElementsByTagName should not be able to access elements removed from ShadowRoot.");
+is(shadow.getElementsByTagNameNS("http://www.w3.org/2000/svg", "svg").length, 0, "ShadowRoot getElementsByTagNameNS should not be able to access elements removed from ShadowRoot.");
+
+// Test querySelector on element in a ShadowRoot.
+element = document.createElement("div");
+shadow = element.createShadowRoot();
+var parentDiv = document.createElement("div");
+var childSpan = document.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 = document.createElement("div");
+shadow = element.createShadowRoot();
+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.");
+
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_shadowroot_host.html b/dom/tests/mochitest/webcomponents/test_shadowroot_host.html
new file mode 100644
index 000000000..f48d63e87
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_host.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1083587
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1083587</title>
+ <script type="application/javascript" 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=1083587">Mozilla Bug 1083587</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<div id="host"></div>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+
+/** Test for Bug 1083587 **/
+var hostInDoc = document.getElementById("host");
+var shadowOne = hostInDoc.createShadowRoot();
+is(shadowOne.host, hostInDoc);
+
+var shadowTwo = hostInDoc.createShadowRoot();
+is(shadowOne.host, hostInDoc);
+is(shadowTwo.host, hostInDoc);
+
+var hostNotInDoc = document.createElement("div");
+var shadowThree = hostNotInDoc.createShadowRoot();
+is(shadowThree.host, hostNotInDoc);
+
+var shadowFour = hostNotInDoc.createShadowRoot();
+is(shadowThree.host, hostNotInDoc);
+is(shadowFour.host, hostNotInDoc);
+
+</script>
+</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 000000000..ca525adad
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html
@@ -0,0 +1,44 @@
+<!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="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="runChecks();">
+<div id="grabme"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
+<script>
+
+var element = document.getElementById("grabme");
+var shadow = element.createShadowRoot();
+
+// Check that <base> is inert.
+shadow.innerHTML = '<base href="http://www.example.org/" />';
+isnot(document.baseURI, "http://www.example.org/", "Base element should be inert in ShadowRoot.");
+
+SimpleTest.waitForExplicitFinish();
+
+// Check that <link> is inert.
+var numStyleBeforeLoad = document.styleSheets.length;
+
+shadow.innerHTML = '<link id="shadowlink" rel="stylesheet" type="text/css" href="inert_style.css" /><span id="shadowspan"></span>';
+shadow.applyAuthorStyles = true;
+var shadowSpan = shadow.getElementById("shadowspan");
+var shadowStyle = shadow.getElementById("shadowlink");
+
+function runChecks() {
+ isnot(getComputedStyle(shadowSpan, null).getPropertyValue("padding-top"), "10px", "Link element should be inert.");
+ is(document.styleSheets.length, numStyleBeforeLoad, "Document style count should remain the same because the style should not be in the doucment.");
+ is(shadow.styleSheets.length, 0, "Inert link should not add style to ShadowRoot.");
+ // Remove link to make sure we don't get assertions.
+ shadow.removeChild(shadowStyle);
+ 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 000000000..f310ff97c
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=806506
+-->
+<head>
+ <title>Test for ShadowRoot styling</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div class="tall" id="bodydiv"></div>
+<div id="container"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
+<script>
+// Create ShadowRoot.
+var container = document.getElementById("container");
+var elem = document.createElement("div");
+container.appendChild(elem); // Put ShadowRoot host in document.
+var root = elem.createShadowRoot();
+
+// A style element that will be appended into the ShadowRoot.
+var shadowStyle = document.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(document.styleSheets.length, 1, "There should only be one style sheet on the document from the test style sheet.");
+is(root.styleSheets.length, 0, "The ShadowRoot should have no style sheets.");
+root.appendChild(shadowStyle);
+is(document.styleSheets.length, 1, "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 = document.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(getComputedStyle(document.getElementById("bodydiv"), null).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(getComputedStyle(divToStyle, null).getPropertyValue("height"), "100px", "ShadowRoot style sheets should apply to elements in ShadowRoot.");
+
+// Tests for applyAuthorStyles.
+var authorStyle = document.createElement("style");
+authorStyle.innerHTML = ".fat { padding-right: 20px; padding-left: 30px; }";
+document.body.appendChild(authorStyle);
+
+is(root.applyAuthorStyles, false, "applyAuthorStyles defaults to false.");
+isnot(getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should not apply to ShadowRoot when ShadowRoot.applyAuthorStyles is false.");
+root.applyAuthorStyles = true;
+is(root.applyAuthorStyles, true, "applyAuthorStyles was set to true.");
+is(getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should apply to ShadowRoot when ShadowRoot.applyAuthorStyles is true.");
+root.applyAuthorStyles = false;
+is(root.applyAuthorStyles, false, "applyAuthorStyles was set to false.");
+isnot(getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should not apply to ShadowRoot when ShadowRoot.applyAuthorStyles is false.");
+
+// Test dynamic changes to style in ShadowRoot.
+root.innerHTML = '<div id="divtostyle" class="dummy"></div>';
+divToStyle = root.getElementById("divtostyle");
+var dummyShadowStyle = document.createElement("style");
+dummyShadowStyle.innerHTML = ".dummy { height: 300px; }";
+root.appendChild(dummyShadowStyle);
+is(getComputedStyle(divToStyle, null).getPropertyValue("height"), "300px", "Dummy element in ShadowRoot should be styled by style in ShadowRoot.");
+dummyShadowStyle.innerHTML = ".dummy { height: 200px; }";
+is(getComputedStyle(divToStyle, null).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(getComputedStyle(divToStyle, null).getPropertyValue("padding-top"), "10px", "ID selector in style selector should match element.");
+
+</script>
+</body>
+</html>
+
diff --git a/dom/tests/mochitest/webcomponents/test_shadowroot_style_multiple_shadow.html b/dom/tests/mochitest/webcomponents/test_shadowroot_style_multiple_shadow.html
new file mode 100644
index 000000000..7a606bcd7
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style_multiple_shadow.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=806506
+-->
+<head>
+ <title>Test for ShadowRoot styles with multiple ShadowRoot on host.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div class="tall" id="bodydiv"></div>
+<div id="container"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
+<script>
+// Create ShadowRoot.
+var container = document.getElementById("container");
+var elem = document.createElement("div");
+container.appendChild(elem); // Put ShadowRoot host in document.
+var firstRoot = elem.createShadowRoot();
+var secondRoot = elem.createShadowRoot();
+var thirdRoot = elem.createShadowRoot();
+
+// A style element that will be appended into the ShadowRoot.
+var firstStyle = document.createElement("style");
+firstRoot.appendChild(firstStyle);
+is(firstRoot.styleSheets.length, 1, "firstStyle should be the only style in firstRoot.");
+is(firstRoot.styleSheets[0].ownerNode, firstStyle, "firstStyle should in the ShadowRoot styleSheets.");
+
+var secondStyle = document.createElement("style");
+secondRoot.appendChild(secondStyle);
+is(secondRoot.styleSheets.length, 1, "secondStyle should be the only style in secondRoot.");
+is(secondRoot.styleSheets[0].ownerNode, secondStyle, "secondStyle should in the ShadowRoot styleSheets.");
+
+var thirdStyle = document.createElement("style");
+thirdRoot.appendChild(thirdStyle);
+is(thirdRoot.styleSheets.length, 1, "thirdStyle should be the only style in thirdRoot.");
+is(thirdRoot.styleSheets[0].ownerNode, thirdStyle, "thirdStyle should in the ShadowRoot styleSheets.");
+
+// Check the stylesheet counts again to make sure that none of the style sheets leaked into the older ShadowRoots.
+is(firstRoot.styleSheets.length, 1, "Adding a stylesheet to a younger ShadowRoot should not affect stylesheets in the older ShadowRoot.");
+is(secondRoot.styleSheets.length, 1, "Adding a stylesheet to a younger ShadowRoot should not affect stylesheets in the older ShadowRoot.");
+
+// Remove styles and make sure they are removed from the correct ShadowRoot.
+firstRoot.removeChild(firstStyle);
+is(firstRoot.styleSheets.length, 0, "firstRoot should no longer have any styles.");
+
+thirdRoot.removeChild(thirdStyle);
+is(thirdRoot.styleSheets.length, 0, "thirdRoot should no longer have any styles.");
+
+secondRoot.removeChild(secondStyle);
+is(secondRoot.styleSheets.length, 0, "secondRoot should no longer have any styles.");
+
+</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 000000000..524b9bcca
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style_order.html
@@ -0,0 +1,42 @@
+<!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="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="container"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
+<script>
+// Create ShadowRoot.
+var container = document.getElementById("container");
+var elem = document.createElement("div");
+container.appendChild(elem); // Put ShadowRoot host in document.
+var root = elem.createShadowRoot();
+
+// Style elements that will be appended into the ShadowRoot.
+var tallShadowStyle = document.createElement("style");
+tallShadowStyle.innerHTML = ".tall { height: 100px; }";
+
+var veryTallShadowStyle = document.createElement("style");
+veryTallShadowStyle.innerHTML = ".tall { height: 200px; }";
+
+var divToStyle = document.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(window.getComputedStyle(divToStyle, null).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(window.getComputedStyle(divToStyle, null).getPropertyValue("height"), "200px", "Style in ShadowRoot should apply to elements in ShadowRoot in tree order.");
+
+</script>
+</body>
+</html>
diff --git a/dom/tests/mochitest/webcomponents/test_shadowroot_youngershadowroot.html b/dom/tests/mochitest/webcomponents/test_shadowroot_youngershadowroot.html
new file mode 100644
index 000000000..17743321b
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_youngershadowroot.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1083587
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1083587</title>
+ <script type="application/javascript" 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=1083587">Mozilla Bug 1083587</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<div id="host"></div>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+
+/** Test for Bug 1083587 **/
+var hostInDoc = document.getElementById("host");
+var shadowOne = hostInDoc.createShadowRoot();
+is(shadowOne.olderShadowRoot, null);
+
+var shadowTwo = hostInDoc.createShadowRoot();
+is(shadowOne.olderShadowRoot, null);
+is(shadowTwo.olderShadowRoot, shadowOne);
+
+var hostNotInDoc = document.createElement("div");
+var shadowThree = hostNotInDoc.createShadowRoot();
+is(shadowThree.olderShadowRoot, null);
+
+var shadowFour = hostNotInDoc.createShadowRoot();
+is(shadowThree.olderShadowRoot, null);
+is(shadowFour.olderShadowRoot, shadowThree);
+
+</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 000000000..e8d6e6c72
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_style_fallback_content.html
@@ -0,0 +1,28 @@
+<!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="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="grabme"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
+<script>
+var host = document.getElementById("grabme");
+var shadow = host.createShadowRoot();
+shadow.innerHTML = '<style id="innerstyle"></style><span id="container"><content><span id="innerspan">Hello</span></content></span>';
+var innerStyle = shadow.getElementById("innerstyle");
+
+innerStyle.innerHTML = '#innerspan { margin-top: 10px; }';
+var innerSpan = shadow.getElementById("innerspan");
+is(getComputedStyle(innerSpan, null).getPropertyValue("margin-top"), "10px", "Default content should be style by id selector.");
+
+innerStyle.innerHTML = '#container > content > #innerspan { margin-top: 30px; }';
+is(getComputedStyle(innerSpan, null).getPropertyValue("margin-top"), "30px", "Default content should be style by child combinators.");
+</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 000000000..a43e3fba5
--- /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 type="text/javascript" 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_custom_elements.html b/dom/tests/mochitest/webcomponents/test_template_custom_elements.html
new file mode 100644
index 000000000..f7f4340cf
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_template_custom_elements.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1091425
+-->
+<head>
+ <title>Test for custom elements in template</title>
+ <script type="text/javascript" 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>
+
+var p = {};
+p.createdCallback = function() {
+ ok(false, "Created callback should not be called for custom elements in templates.");
+};
+
+document.registerElement("x-foo", { prototype: p });
+
+ok(true, "Created 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_template_xhtml.html b/dom/tests/mochitest/webcomponents/test_template_xhtml.html
new file mode 100644
index 000000000..233ed5b76
--- /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 type="text/javascript" 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_unresolved_pseudo_class.html b/dom/tests/mochitest/webcomponents/test_unresolved_pseudo_class.html
new file mode 100644
index 000000000..3e1fae8ee
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_unresolved_pseudo_class.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1111633
+-->
+<head>
+ <title>Test template element in stale document.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ :unresolved {
+ color: rgb(0, 0, 255);
+ background-color: rgb(0, 0, 255);
+ }
+
+ x-foo { color: rgb(255, 0, 0); }
+
+ [is="x-del"]:not(:unresolved) { color: rgb(255, 0, 0); }
+
+ [is="x-bar"]:not(:unresolved) { color: rgb(255, 0, 0); }
+
+ [is="x-bar"]:unresolved { background-color: rgb(255, 0, 0); }
+
+ x-baz:not(:unresolved) {
+ color: rgb(255, 0, 0);
+ background-color: rgb(255, 0, 0);
+ }
+
+ span { color: rgb(0,255,0); }
+
+ x-foo:unresolved + span { color: rgb(255,0,0); }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1111633">Bug 1111633</a>
+<div id="container"></div>
+<x-foo id="foo"></x-foo>
+<span id="span1">This text should be green</span>
+<span id="bar" is="x-bar"></span>
+<x-baz id="baz"></x-baz>
+<span id="del" is="x-del"></span>
+<script>
+
+// Before registerElement
+var foo = document.querySelector('#foo');
+is(getComputedStyle(foo).color, "rgb(0, 0, 255)", "foo - color");
+is(getComputedStyle(foo).backgroundColor, "rgb(0, 0, 255)", "foo - backgroundColor");
+
+var bar = document.querySelector('#bar');
+is(getComputedStyle(bar).color, "rgb(0, 0, 255)", "bar - color");
+is(getComputedStyle(bar).backgroundColor, "rgb(255, 0, 0)", "bar - backgroundColor");
+
+var baz = document.querySelector('#baz');
+is(getComputedStyle(baz).color, "rgb(0, 0, 255)", "baz - color");
+is(getComputedStyle(baz).backgroundColor, "rgb(0, 0, 255)", "baz - backgroundColor");
+
+var span1 = document.querySelector('#span1');
+is(getComputedStyle(span1).color, "rgb(255, 0, 0)", "span1 - color");
+
+var Foo = document.registerElement('x-foo', { prototype: Object.create(HTMLElement.prototype) });
+
+var Bar = document.registerElement('x-bar', { extends: 'span', prototype: Object.create(HTMLSpanElement.prototype) });
+
+var Baz = document.registerElement('x-baz', { prototype: Object.create(HTMLElement.prototype) });
+
+// After registerElement
+is(getComputedStyle(foo).color, "rgb(255, 0, 0)",
+ "foo - color (after registerElement)");
+
+is(getComputedStyle(bar).color,
+ "rgb(255, 0, 0)", "bar - color (after registerElement)");
+
+is(getComputedStyle(baz).color,
+ "rgb(255, 0, 0)", "baz - color (after registerElement)");
+is(getComputedStyle(baz).backgroundColor,
+ "rgb(255, 0, 0)", "baz - backgroundColor (after registerElement)");
+
+is(getComputedStyle(span1).color, "rgb(0, 255, 0)", "span1 - color (after registerElement)");
+
+// After tree removal
+var del = document.querySelector('#del');
+is(getComputedStyle(del).color, "rgb(0, 0, 255)", "del - color");
+var par = del.parentNode;
+par.removeChild(del);
+// Changing is attribute after creation should not change the type
+// of a custom element, even if the element was removed and re-append to the tree.
+del.setAttribute("is", "foobar");
+par.appendChild(del);
+is(getComputedStyle(del).color, "rgb(0, 0, 255)", "del - color (after reappend)");
+var Del = document.registerElement('x-del', { extends: 'span', prototype: Object.create(HTMLSpanElement.prototype) });
+// [is="x-del"] will not match any longer so the rule of span will apply
+is(getComputedStyle(del).color, "rgb(0, 255, 0)", "del - color (after registerElement)");
+// but the element should have been upgraded:
+ok(del instanceof Del, "element was upgraded correctly after changing |is|");
+
+</script>
+</body>
+</html>