summaryrefslogtreecommitdiffstats
path: root/accessible/tests/mochitest/events/test_statechange.html
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/tests/mochitest/events/test_statechange.html')
-rw-r--r--accessible/tests/mochitest/events/test_statechange.html565
1 files changed, 565 insertions, 0 deletions
diff --git a/accessible/tests/mochitest/events/test_statechange.html b/accessible/tests/mochitest/events/test_statechange.html
new file mode 100644
index 0000000000..0642851408
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_statechange.html
@@ -0,0 +1,565 @@
+<html>
+
+<head>
+ <title>Accessible state change event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ async function openNode(aIDDetails, aIDSummary, aIsOpen) {
+ let p = waitForStateChange(aIDSummary, STATE_EXPANDED, aIsOpen, false);
+ if (aIsOpen) {
+ getNode(aIDDetails).setAttribute("open", "");
+ } else {
+ getNode(aIDDetails).removeAttribute("open");
+ }
+ await p;
+ }
+
+ async function makeEditableDoc(aDocNode, aIsEnabled) {
+ let p = waitForStateChange(aDocNode, EXT_STATE_EDITABLE, true, true);
+ aDocNode.designMode = "on";
+ await p;
+ }
+
+ async function invalidInput(aNodeOrID) {
+ let p = waitForStateChange(aNodeOrID, STATE_INVALID, true, false);
+ getNode(aNodeOrID).value = "I am not an email";
+ await p;
+ }
+
+ async function changeCheckInput(aID, aIsChecked) {
+ let p = waitForStateChange(aID, STATE_CHECKED, aIsChecked, false);
+ getNode(aID).checked = aIsChecked;
+ await p;
+ }
+
+ async function changeRequiredState(aID, aIsRequired) {
+ let p = waitForStateChange(aID, STATE_REQUIRED, aIsRequired, false);
+ getNode(aID).required = aIsRequired;
+ await p;
+ }
+
+ async function stateChangeOnFileInput(aID, aAttr, aValue,
+ aState, aIsExtraState, aIsEnabled) {
+ let fileControlNode = getNode(aID);
+ let browseButton = getAccessible(fileControlNode);
+ let p = waitForStateChange(
+ browseButton, aState, aIsEnabled, aIsExtraState
+ );
+ fileControlNode.setAttribute(aAttr, aValue);
+ await p;
+ }
+
+ function toggleSentinel() {
+ let sentinel = getNode("sentinel");
+ if (sentinel.hasAttribute("aria-busy")) {
+ sentinel.removeAttribute("aria-busy");
+ } else {
+ sentinel.setAttribute("aria-busy", "true");
+ }
+ }
+
+ async function toggleStateChange(aID, aAttr, aState, aIsExtraState) {
+ let p = waitForEvents([
+ stateChangeEventArgs(aID, aState, true, aIsExtraState),
+ [EVENT_STATE_CHANGE, "sentinel"]
+ ]);
+ getNode(aID).setAttribute(aAttr, "true");
+ toggleSentinel();
+ await p;
+ p = waitForEvents([
+ stateChangeEventArgs(aID, aState, false, aIsExtraState),
+ [EVENT_STATE_CHANGE, "sentinel"]
+ ]);
+ getNode(aID).setAttribute(aAttr, "false");
+ toggleSentinel();
+ await p;
+ }
+
+ async function dupeStateChange(aID, aAttr, aValue,
+ aState, aIsExtraState, aIsEnabled) {
+ let p = waitForEvents([
+ stateChangeEventArgs(aID, aState, aIsEnabled, aIsExtraState),
+ [EVENT_STATE_CHANGE, "sentinel"]
+ ]);
+ getNode(aID).setAttribute(aAttr, aValue);
+ getNode(aID).setAttribute(aAttr, aValue);
+ toggleSentinel();
+ await p;
+ }
+
+ async function oppositeStateChange(aID, aAttr, aState, aIsExtraState) {
+ let p = waitForEvents({
+ expected: [[EVENT_STATE_CHANGE, "sentinel"]],
+ unexpected: [
+ stateChangeEventArgs(aID, aState, false, aIsExtraState),
+ stateChangeEventArgs(aID, aState, true, aIsExtraState)
+ ]
+ });
+ getNode(aID).setAttribute(aAttr, "false");
+ getNode(aID).setAttribute(aAttr, "true");
+ toggleSentinel();
+ await p;
+ }
+
+ /**
+ * Change concomitant ARIA and native attribute at once.
+ */
+ async function echoingStateChange(aID, aARIAAttr, aAttr, aValue,
+ aState, aIsExtraState, aIsEnabled) {
+ let p = waitForStateChange(aID, aState, aIsEnabled, aIsExtraState);
+ if (aValue == null) {
+ getNode(aID).removeAttribute(aARIAAttr);
+ getNode(aID).removeAttribute(aAttr);
+ } else {
+ getNode(aID).setAttribute(aARIAAttr, aValue);
+ getNode(aID).setAttribute(aAttr, aValue);
+ }
+ await p;
+ }
+
+ async function testHasPopup() {
+ let p = waitForStateChange("popupButton", STATE_HASPOPUP, true, false);
+ getNode("popupButton").setAttribute("aria-haspopup", "true");
+ await p;
+
+ p = waitForStateChange("popupButton", STATE_HASPOPUP, false, false);
+ getNode("popupButton").setAttribute("aria-haspopup", "false");
+ await p;
+
+ p = waitForStateChange("popupButton", STATE_HASPOPUP, true, false);
+ getNode("popupButton").setAttribute("aria-haspopup", "true");
+ await p;
+
+ p = waitForStateChange("popupButton", STATE_HASPOPUP, false, false);
+ getNode("popupButton").removeAttribute("aria-haspopup");
+ await p;
+ }
+
+ async function testDefaultSubmitChange() {
+ testStates("default-button",
+ STATE_DEFAULT, 0,
+ 0, 0,
+ "button should have DEFAULT state");
+ let button = document.createElement("button");
+ button.textContent = "new default";
+ let p = waitForStateChange("default-button", STATE_DEFAULT, false, false);
+ getNode("default-button").before(button);
+ await p;
+ testStates("default-button",
+ 0, 0,
+ STATE_DEFAULT, 0,
+ "button should not have DEFAULT state");
+ p = waitForStateChange("default-button", STATE_DEFAULT, true, false);
+ button.remove();
+ await p;
+ testStates("default-button",
+ STATE_DEFAULT, 0,
+ 0, 0,
+ "button should have DEFAULT state");
+ }
+
+ async function testReadOnly() {
+ let p = waitForStateChange("email", STATE_READONLY, true, false);
+ getNode("email").setAttribute("readonly", "true");
+ await p;
+ p = waitForStateChange("email", STATE_READONLY, false, false);
+ getNode("email").removeAttribute("readonly");
+ await p;
+ }
+
+ async function testReadonlyUntilEditable() {
+ testStates("article",
+ STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE,
+ "article is READONLY and not EDITABLE");
+ let p = waitForEvents([
+ stateChangeEventArgs("article", STATE_READONLY, false, false),
+ stateChangeEventArgs("article", EXT_STATE_EDITABLE, true, true)]);
+ getNode("article").contentEditable = "true";
+ await p;
+ testStates("article",
+ 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0,
+ "article is EDITABLE and not READONLY");
+ p = waitForEvents([
+ stateChangeEventArgs("article", STATE_READONLY, true, false),
+ stateChangeEventArgs("article", EXT_STATE_EDITABLE, false, true)]);
+ getNode("article").contentEditable = "false";
+ await p;
+ testStates("article",
+ STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE,
+ "article is READONLY and not EDITABLE");
+ }
+
+ async function testAnimatedImage() {
+ testStates("animated-image",
+ STATE_ANIMATED, 0,
+ 0, 0,
+ "image should be animated 1");
+ let p = waitForStateChange("animated-image", STATE_ANIMATED, false, false);
+ getNode("animated-image").src = "../animated-gif-finalframe.gif";
+ await p;
+ testStates("animated-image",
+ 0, 0,
+ STATE_ANIMATED, 0,
+ "image should not be animated 2");
+ p = waitForStateChange("animated-image", STATE_ANIMATED, true, false);
+ getNode("animated-image").src = "../animated-gif.gif";
+ await p;
+ testStates("animated-image",
+ STATE_ANIMATED, 0,
+ 0, 0,
+ "image should be animated 3");
+ }
+
+ async function testImageLoad() {
+ let img = document.createElement("img");
+ img.id = "image";
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ img.src = "http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs";
+ let p = waitForEvent(EVENT_SHOW, "image");
+ getNode("eventdump").before(img);
+ await p;
+ testStates("image",
+ STATE_INVISIBLE, 0,
+ 0, 0,
+ "image should be invisible");
+ p = waitForStateChange("image", STATE_INVISIBLE, false, false);
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await fetch("http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs?complete");
+ await p;
+ testStates("image",
+ 0, 0,
+ STATE_INVISIBLE, 0,
+ "image should be invisible");
+ }
+
+ async function testMultiSelectable(aID, aAttribute) {
+ testStates(aID,
+ 0, 0,
+ STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0,
+ `${aID} should not be multiselectable`);
+ let p = waitForEvents([
+ stateChangeEventArgs(aID, STATE_MULTISELECTABLE, true, false),
+ stateChangeEventArgs(aID, STATE_EXTSELECTABLE, true, false),
+ ]);
+ getNode(aID).setAttribute(aAttribute, true);
+ await p;
+ testStates(aID,
+ STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0,
+ 0, 0,
+ `${aID} should not be multiselectable`);
+ p = waitForEvents([
+ stateChangeEventArgs(aID, STATE_MULTISELECTABLE, false, false),
+ stateChangeEventArgs(aID, STATE_EXTSELECTABLE, false, false),
+ ]);
+ getNode(aID).removeAttribute(aAttribute);
+ await p;
+ testStates(aID,
+ 0, 0,
+ STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0,
+ `${aID} should not be multiselectable`);
+ }
+
+ async function testAutocomplete() {
+ // A text input will have autocomplete via browser's form autofill...
+ testStates("input",
+ 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ 0, 0,
+ "input supports autocompletion");
+ // unless it is explicitly turned off.
+ testStates("input-autocomplete-off",
+ 0, 0,
+ 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "input-autocomplete-off does not support autocompletion");
+ // An input with a datalist will always have autocomplete.
+ testStates("input-list",
+ 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ 0, 0,
+ "input-list supports autocompletion");
+ // password fields don't get autocomplete.
+ testStates("input-password",
+ 0, 0,
+ 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "input-autocomplete-off does not support autocompletion");
+
+ let p = waitForEvents({
+ expected: [
+ // Setting the form's autocomplete attribute to "off" will cause
+ // "input" to lost its autocomplete state.
+ stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, false, true)
+ ],
+ unexpected: [
+ // "input-list" should preserve its autocomplete state regardless of
+ // forms "autocomplete" attribute
+ [EVENT_STATE_CHANGE, "input-list"],
+ // "input-autocomplete-off" already has its autocomplte off, so no state
+ // change here.
+ [EVENT_STATE_CHANGE, "input-autocomplete-off"],
+ // passwords never get autocomplete
+ [EVENT_STATE_CHANGE, "input-password"],
+ ]
+ });
+
+ getNode("form").setAttribute("autocomplete", "off");
+
+ await p;
+
+ // Same when we remove the form's autocomplete attribute.
+ p = waitForEvents({
+ expected: [stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, true, true)],
+ unexpected: [
+ [EVENT_STATE_CHANGE, "input-list"],
+ [EVENT_STATE_CHANGE, "input-autocomplete-off"],
+ [EVENT_STATE_CHANGE, "input-password"],
+ ]
+ });
+
+ getNode("form").removeAttribute("autocomplete");
+
+ await p;
+
+ p = waitForEvents({
+ expected: [
+ // Forcing autocomplete off on an input will cause a state change
+ stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, false, true),
+ // Associating a datalist with an autocomplete=off input
+ // will give it an autocomplete state, regardless.
+ stateChangeEventArgs("input-autocomplete-off", EXT_STATE_SUPPORTS_AUTOCOMPLETION, true, true),
+ // XXX: datalist inputs also get a HASPOPUP state, the inconsistent
+ // use of that state is inexplicable, but lets make sure we fire state
+ // change events for it anyway.
+ stateChangeEventArgs("input-autocomplete-off", STATE_HASPOPUP, true, false),
+ ],
+ unexpected: [
+ // Forcing autocomplete off with a dataset input does nothing.
+ [EVENT_STATE_CHANGE, "input-list"],
+ // passwords never get autocomplete
+ [EVENT_STATE_CHANGE, "input-password"],
+ ]
+ });
+
+ getNode("input").setAttribute("autocomplete", "off");
+ getNode("input-list").setAttribute("autocomplete", "off");
+ getNode("input-autocomplete-off").setAttribute("list", "browsers");
+ getNode("input-password").setAttribute("autocomplete", "off");
+
+ await p;
+ }
+
+ async function doTests() {
+ // Disable mixed-content upgrading as this test is expecting an HTTP load
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.mixed_content.upgrade_display_content", false]]
+ });
+
+ // Test opening details objects
+ await openNode("detailsOpen", "summaryOpen", true);
+ await openNode("detailsOpen", "summaryOpen", false);
+ await openNode("detailsOpen1", "summaryOpen1", true);
+ await openNode("detailsOpen2", "summaryOpen2", true);
+ await openNode("detailsOpen3", "summaryOpen3", true);
+ await openNode("detailsOpen4", "summaryOpen4", true);
+ await openNode("detailsOpen5", "summaryOpen5", true);
+ await openNode("detailsOpen6", "summaryOpen6", true);
+
+ // Test delayed editable state change
+ var doc = document.getElementById("iframe").contentDocument;
+ await makeEditableDoc(doc);
+
+ // invalid state change
+ await invalidInput("email");
+
+ // checked state change
+ await changeCheckInput("checkbox", true);
+ await changeCheckInput("checkbox", false);
+ await changeCheckInput("radio", true);
+ await changeCheckInput("radio", false);
+
+ // required state change
+ await changeRequiredState("checkbox", true);
+
+ // file input inherited state changes
+ await stateChangeOnFileInput("file", "aria-busy", "true",
+ STATE_BUSY, false, true);
+ await stateChangeOnFileInput("file", "aria-required", "true",
+ STATE_REQUIRED, false, true);
+ await stateChangeOnFileInput("file", "aria-invalid", "true",
+ STATE_INVALID, false, true);
+
+ await dupeStateChange("div", "aria-busy", "true",
+ STATE_BUSY, false, true);
+ await oppositeStateChange("div", "aria-busy",
+ STATE_BUSY, false);
+
+ await echoingStateChange("text1", "aria-disabled", "disabled", "true",
+ EXT_STATE_ENABLED, true, false);
+ await echoingStateChange("text1", "aria-disabled", "disabled", null,
+ EXT_STATE_ENABLED, true, true);
+
+ await testReadOnly();
+
+ await testReadonlyUntilEditable();
+
+ await testHasPopup();
+
+ await toggleStateChange("textbox", "aria-multiline", EXT_STATE_MULTI_LINE, true);
+
+ await testDefaultSubmitChange();
+
+ await testAnimatedImage();
+
+ await testImageLoad();
+
+ await testMultiSelectable("listbox", "aria-multiselectable");
+
+ await testMultiSelectable("select", "multiple");
+
+ await testAutocomplete();
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+<style>
+ details.openBefore::before{
+ content: "before detail content: ";
+ background: blue;
+ }
+ summary.openBefore::before{
+ content: "before summary content: ";
+ background: green;
+ }
+ details.openAfter::after{
+ content: " :after detail content";
+ background: blue;
+ }
+ summary.openAfter::after{
+ content: " :after summary content";
+ background: green;
+ }
+</style>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=564471"
+ title="Make state change events async">
+ Bug 564471
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=555728"
+ title="Fire a11y event based on HTML5 constraint validation">
+ Bug 555728
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017"
+ title="File input control should be propogate states to descendants">
+ Bug 699017
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=788389"
+ title="Fire statechange event whenever checked state is changed not depending on focused state">
+ Bug 788389
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=926812"
+ title="State change event not fired when both disabled and aria-disabled are toggled">
+ Bug 926812
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- open -->
+ <details id="detailsOpen"><summary id="summaryOpen">open</summary>details can be opened</details>
+ <details id="detailsOpen1">order doesn't matter<summary id="summaryOpen1">open</summary></details>
+ <details id="detailsOpen2"><div>additional elements don't matter</div><summary id="summaryOpen2">open</summary></details>
+ <details id="detailsOpen3" class="openBefore"><summary id="summaryOpen3">summary</summary>content</details>
+ <details id="detailsOpen4" class="openAfter"><summary id="summaryOpen4">summary</summary>content</details>
+ <details id="detailsOpen5"><summary id="summaryOpen5" class="openBefore">summary</summary>content</details>
+ <details id="detailsOpen6"><summary id="summaryOpen6" class="openAfter">summary</summary>content</details>
+
+
+ <div id="testContainer">
+ <iframe id="iframe"></iframe>
+ </div>
+
+ <input id="email" type='email'>
+
+ <input id="checkbox" type="checkbox">
+ <input id="radio" type="radio">
+
+ <input id="file" type="file">
+
+ <div id="div"></div>
+
+ <!-- A sentinal guards from events of interest being fired after it emits a state change -->
+ <div id="sentinel"></div>
+
+ <input id="text1">
+
+ <div id="textbox" role="textbox" aria-multiline="false">hello</div>
+
+ <form id="form">
+ <button id="default-button">hello</button>
+ <button>world</button>
+ <input id="input">
+ <input id="input-autocomplete-off" autocomplete="off">
+ <input id="input-list" list="browsers">
+ <input id="input-password" type="password">
+ <datalist id="browsers">
+ <option value="Internet Explorer">
+ <option value="Firefox">
+ <option value="Google Chrome">
+ <option value="Opera">
+ <option value="Safari">
+ </datalist>
+ </form>
+
+ <div id="article" role="article">hello</div>
+
+ <img id="animated-image" src="../animated-gif.gif">
+
+ <ul id="listbox" role="listbox">
+ <li role="option">one</li>
+ <li role="option">two</li>
+ <li role="option">three</li>
+ <li role="option">four</li>
+ <li role="option">five</li>
+ </ul>
+
+ <select id="select" size="2">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+ <option>four</option>
+ <option>five</option>
+ <option>size</option>
+ </select>
+
+ <div id="eventdump"></div>
+
+ <div id="eventdump"></div>
+ <button id="popupButton">action</button>
+</body>
+</html>