diff options
Diffstat (limited to 'dom/tests/mochitest/webcomponents')
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> |