diff options
Diffstat (limited to 'toolkit/content/tests/chrome/test_custom_element_base.xhtml')
-rw-r--r-- | toolkit/content/tests/chrome/test_custom_element_base.xhtml | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/toolkit/content/tests/chrome/test_custom_element_base.xhtml b/toolkit/content/tests/chrome/test_custom_element_base.xhtml new file mode 100644 index 0000000000..77cc8819a3 --- /dev/null +++ b/toolkit/content/tests/chrome/test_custom_element_base.xhtml @@ -0,0 +1,363 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Custom Element Base Class Tests" + onload="runTests();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <button id="one"/> + <simpleelement id="two" style="-moz-user-focus: normal;"/> + <simpleelement id="three" disabled="true" style="-moz-user-focus: normal;"/> + <button id="four"/> + <inherited-element-declarative foo="fuagra" empty-string=""></inherited-element-declarative> + <inherited-element-derived foo="fuagra"></inherited-element-derived> + <inherited-element-shadowdom-declarative foo="fuagra" empty-string=""></inherited-element-shadowdom-declarative> + <inherited-element-imperative foo="fuagra" empty-string=""></inherited-element-imperative> + <inherited-element-beforedomloaded foo="fuagra" empty-string=""></inherited-element-beforedomloaded> + + <!-- test code running before page load goes here --> + <script type="application/javascript"><![CDATA[ + class InheritAttrsChangeBeforDOMLoaded extends MozXULElement { + static get inheritedAttributes() { + return { + "label": "foo", + }; + } + connectedCallback() { + this.append(MozXULElement.parseXULToFragment(`<label />`)); + this.label = this.querySelector("label"); + + this.initializeAttributeInheritance(); + is(this.label.getAttribute("foo"), "fuagra", + "InheritAttrsChangeBeforDOMLoaded: attribute should be propagated #1"); + + this.setAttribute("foo", "chuk&gek"); + is(this.label.getAttribute("foo"), "chuk&gek", + "InheritAttrsChangeBeforDOMLoaded: attribute should be propagated #2"); + } + } + customElements.define("inherited-element-beforedomloaded", + InheritAttrsChangeBeforDOMLoaded); + ]]> + </script> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + async function runTests() { + ok(MozXULElement, "MozXULElement defined on the window"); + testMixin(); + testBaseControl(); + testBaseControlMixin(); + testBaseText(); + testParseXULToFragment(); + testInheritAttributes(); + await testCustomInterface(); + + let htmlWin = await new Promise(resolve => { + let htmlIframe = document.createXULElement("iframe"); + htmlIframe.src = "file_empty.xhtml"; + htmlIframe.onload = () => resolve(htmlIframe.contentWindow); + document.documentElement.appendChild(htmlIframe); + }); + + ok(htmlWin.MozXULElement, "MozXULElement defined on a chrome HTML window"); + SimpleTest.finish(); + } + + function testMixin() { + ok(MozElements.MozElementMixin, "Mixin exists"); + let MixedHTMLElement = MozElements.MozElementMixin(HTMLElement); + ok(MixedHTMLElement.insertFTLIfNeeded, "Mixed in class contains helper functions"); + } + + function testBaseControl() { + ok(MozElements.BaseControl, "BaseControl exists"); + ok("disabled" in MozElements.BaseControl.prototype, + "BaseControl prototype contains base control attributes"); + } + + function testBaseControlMixin() { + ok(MozElements.BaseControlMixin, "Mixin exists"); + let MixedHTMLSpanElement = MozElements.MozElementMixin(HTMLSpanElement); + let HTMLSpanBaseControl = MozElements.BaseControlMixin(MixedHTMLSpanElement); + ok("disabled" in HTMLSpanBaseControl.prototype, "Mixed in class prototype contains base control attributes"); + } + + function testBaseText() { + ok(MozElements.BaseText, "BaseText exists"); + ok("label" in MozElements.BaseText.prototype, + "BaseText prototype inherits BaseText attributes"); + ok("disabled" in MozElements.BaseText.prototype, + "BaseText prototype inherits BaseControl attributes"); + } + + function testParseXULToFragment() { + ok(MozXULElement.parseXULToFragment, "parseXULToFragment helper exists"); + + let frag = MozXULElement.parseXULToFragment(`<deck id='foo' />`); + ok(DocumentFragment.isInstance(frag)); + + document.documentElement.appendChild(frag); + + let deck = document.documentElement.lastChild; + ok(deck instanceof MozXULElement, "instance of MozXULElement"); + ok(XULElement.isInstance(deck), "instance of XULElement"); + is(deck.id, "foo", "attribute set"); + is(deck.selectedIndex, 0, "Custom Element is property attached"); + deck.remove(); + + info("Checking that whitespace text is removed but non-whitespace text isn't"); + let boxWithWhitespaceText = MozXULElement.parseXULToFragment(`<box> </box>`).querySelector("box"); + is(boxWithWhitespaceText.textContent, "", "Whitespace removed"); + let boxWithNonWhitespaceText = MozXULElement.parseXULToFragment(`<box>foo</box>`).querySelector("box"); + is(boxWithNonWhitespaceText.textContent, "foo", "Non-whitespace not removed"); + + try { + // we didn't encode the & as & + MozXULElement.parseXULToFragment(`<box id="foo=1&bar=2"/>`); + ok(false, "parseXULToFragment should've thrown an exception for not-well-formed XML"); + } + catch (ex) { + is(ex.message, "not well-formed XML", "parseXULToFragment threw the wrong message"); + } + } + + function testInheritAttributes() { + class InheritsElementDeclarative extends MozXULElement { + static get inheritedAttributes() { + return { + "label": "text=label,foo,empty-string,bardo=bar", + "unmatched": "foo", // Make sure we don't throw on unmatched selectors + }; + } + + connectedCallback() { + this.textContent = ""; + this.append(MozXULElement.parseXULToFragment(`<label />`)); + this.label = this.querySelector("label"); + this.initializeAttributeInheritance(); + } + } + customElements.define("inherited-element-declarative", InheritsElementDeclarative); + let declarativeEl = document.querySelector("inherited-element-declarative"); + ok(declarativeEl, "declarative inheritance element exists"); + + class InheritsElementDerived extends InheritsElementDeclarative { + static get inheritedAttributes() { + return { label: "renamedfoo=foo" }; + } + } + customElements.define("inherited-element-derived", InheritsElementDerived); + + class InheritsElementShadowDOMDeclarative extends MozXULElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + } + static get inheritedAttributes() { + return { + "label": "text=label,foo,empty-string,bardo=bar", + "unmatched": "foo", // Make sure we don't throw on unmatched selectors + }; + } + + connectedCallback() { + this.shadowRoot.textContent = ""; + this.shadowRoot.append(MozXULElement.parseXULToFragment(`<label />`)); + this.label = this.shadowRoot.querySelector("label"); + this.initializeAttributeInheritance(); + } + } + customElements.define("inherited-element-shadowdom-declarative", InheritsElementShadowDOMDeclarative); + let shadowDOMDeclarativeEl = document.querySelector("inherited-element-shadowdom-declarative"); + ok(shadowDOMDeclarativeEl, "declarative inheritance element with shadow DOM exists"); + + class InheritsElementImperative extends MozXULElement { + static get observedAttributes() { + return [ "label", "foo", "empty-string", "bar" ]; + } + + attributeChangedCallback(name, oldValue, newValue) { + if (this.label && oldValue != newValue) { + this.inherit(); + } + } + + inherit() { + let map = { + "label": [[ "label", "text" ]], + "foo": [[ "label", "foo" ]], + "empty-string": [[ "label", "empty-string" ]], + "bar": [[ "label", "bardo" ]], + }; + for (let attr of InheritsElementImperative.observedAttributes) { + this.inheritAttribute(map[attr], attr); + } + } + + connectedCallback() { + // Typically `initializeAttributeInheritance` handles this for us: + this._inheritedElements = null; + + this.textContent = ""; + this.append(MozXULElement.parseXULToFragment(`<label />`)); + this.label = this.querySelector("label"); + this.inherit(); + } + } + + customElements.define("inherited-element-imperative", InheritsElementImperative); + let imperativeEl = document.querySelector("inherited-element-imperative"); + ok(imperativeEl, "imperative inheritance element exists"); + + function checkElement(el) { + is(el.label.getAttribute("foo"), "fuagra", "predefined attribute @foo"); + ok(el.label.hasAttribute("empty-string"), "predefined attribute @empty-string"); + ok(!el.label.hasAttribute("bardo"), "predefined attribute @bardo"); + ok(!el.label.textContent, "predefined attribute @label"); + + el.setAttribute("empty-string", "not-empty-anymore"); + is(el.label.getAttribute("empty-string"), "not-empty-anymore", + "attribute inheritance: empty-string"); + + el.setAttribute("label", "label-test"); + is(el.label.textContent, "label-test", + "attribute inheritance: text=label attribute change"); + + el.setAttribute("bar", "bar-test"); + is(el.label.getAttribute("bardo"), "bar-test", + "attribute inheritance: `=` mapping"); + + el.label.setAttribute("bardo", "changed-from-child"); + is(el.label.getAttribute("bardo"), "changed-from-child", + "attribute inheritance: doesn't apply when host attr hasn't changed and child attr was changed"); + + el.label.removeAttribute("bardo"); + ok(!el.label.hasAttribute("bardo"), + "attribute inheritance: doesn't apply when host attr hasn't changed and child attr was removed"); + + el.setAttribute("bar", "changed-from-host"); + is(el.label.getAttribute("bardo"), "changed-from-host", + "attribute inheritance: does apply when host attr has changed and child attr was changed"); + + el.removeAttribute("bar"); + ok(!el.label.hasAttribute("bardo"), + "attribute inheritance: does apply when host attr has been removed"); + + el.setAttribute("bar", "changed-from-host-2"); + is(el.label.getAttribute("bardo"), "changed-from-host-2", + "attribute inheritance: does apply when host attr has changed after being removed"); + + // Restore to the original state so this can be ran again with the same element: + el.removeAttribute("label"); + el.removeAttribute("bar"); + } + + for (let el of [declarativeEl, shadowDOMDeclarativeEl, imperativeEl]) { + info(`Running checks for ${el.tagName}`); + checkElement(el); + info(`Remove and re-add ${el.tagName} to make sure attribute inheritance still works`); + el.replaceWith(el); + checkElement(el); + } + + let derivedEl = document.querySelector("inherited-element-derived"); + ok(derivedEl, "derived inheritance element exists"); + ok(!derivedEl.label.hasAttribute("foo"), + "attribute inheritance: base class attribute is not applied in derived class that overrides it"); + ok(derivedEl.label.hasAttribute("renamedfoo"), + "attribute inheritance: attribute defined in derived class is present"); + } + + async function testCustomInterface() { + class SimpleElement extends MozXULElement { + get disabled() { + return this.getAttribute("disabled") == "true"; + } + + set disabled(val) { + if (val) this.setAttribute("disabled", "true"); + else this.removeAttribute("disabled"); + } + + get tabIndex() { + return parseInt(this.getAttribute("tabIndex")) || 0; + } + + set tabIndex(val) { + if (val) this.setAttribute("tabIndex", val); + else this.removeAttribute("tabIndex"); + } + } + + MozXULElement.implementCustomInterface(SimpleElement, [Ci.nsIDOMXULControlElement]); + customElements.define("simpleelement", SimpleElement); + + let twoElement = document.getElementById("two"); + + is(document.documentElement.getCustomInterfaceCallback, undefined, + "No getCustomInterfaceCallback on non-custom element"); + is(typeof twoElement.getCustomInterfaceCallback, "function", + "getCustomInterfaceCallback available on custom element when set"); + is(document.documentElement.QueryInterface, undefined, + "Non-custom element should not have a QueryInterface implementation"); + + // Try various ways to get the custom interface. + + let asControl = twoElement.getCustomInterfaceCallback(Ci.nsIDOMXULControlElement); + + // XXX: Switched to from ok() to todo_is() in Bug 1467712. Follow up in 1500967 + // Not sure if this was suppose to simply check for existence or equality? + todo_is(asControl, twoElement, "getCustomInterface returns interface implementation "); + + asControl = twoElement.QueryInterface(Ci.nsIDOMXULControlElement); + ok(asControl, "QueryInterface to nsIDOMXULControlElement"); + ok(Node.isInstance(asControl), "Control is a Node"); + + // Now make sure that the custom element handles focus/tabIndex as needed by shitfing + // focus around and enabling/disabling the simple elements. + + // Enable Full Keyboard Access emulation on Mac + await SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]}); + + ok(!twoElement.disabled, "two is enabled"); + ok(document.getElementById("three").disabled, "three is disabled"); + + await SimpleTest.promiseFocus(); + ok(document.hasFocus(), "has focus"); + + // This should skip the disabled simpleelement. + synthesizeKey("VK_TAB"); + is(document.activeElement.id, "one", "Tab 1"); + synthesizeKey("VK_TAB"); + is(document.activeElement.id, "two", "Tab 2"); + synthesizeKey("VK_TAB"); + is(document.activeElement.id, "four", "Tab 3"); + + twoElement.disabled = true; + is(twoElement.getAttribute("disabled"), "true", "two disabled after change"); + + synthesizeKey("VK_TAB", { shiftKey: true }); + is(document.activeElement.id, "one", "Tab 1"); + + info("Checking that interfaces get inherited automatically with implementCustomInterface"); + class ExtendedElement extends SimpleElement { } + MozXULElement.implementCustomInterface(ExtendedElement, [Ci.nsIDOMXULSelectControlElement]); + customElements.define("extendedelement", ExtendedElement); + const extendedInstance = document.createXULElement("extendedelement"); + ok(extendedInstance.QueryInterface(Ci.nsIDOMXULSelectControlElement), "interface applied"); + ok(extendedInstance.QueryInterface(Ci.nsIDOMXULControlElement), "inherited interface applied"); + } + ]]> + </script> +</window> |