<
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) {
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, tr
ue)],
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 matteropen
<details id="detailsOpen2"><div>additional elements don't matter