diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /accessible/tests/mochitest/treeupdate | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/tests/mochitest/treeupdate')
41 files changed, 7346 insertions, 0 deletions
diff --git a/accessible/tests/mochitest/treeupdate/a11y.ini b/accessible/tests/mochitest/treeupdate/a11y.ini new file mode 100644 index 0000000000..a40ef0ce05 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/a11y.ini @@ -0,0 +1,46 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + !/accessible/tests/mochitest/moz.png + +[test_ariadialog.html] +[test_ariahidden.html] +[test_ariaowns.html] +[test_bug852150.xhtml] +[test_bug883708.xhtml] +[test_bug884251.xhtml] +[test_bug895082.html] +[test_bug1040735.html] +[test_bug1175913.html] +[test_bug1189277.html] +[test_bug1276857.html] +support-files = test_bug1276857_subframe.html +[test_canvas.html] +[test_contextmenu.xhtml] +[test_cssoverflow.html] +[test_deck.xhtml] +[test_delayed_removal.html] +[test_doc.html] +[test_gencontent.html] +[test_general.html] +[test_hidden.html] +[test_imagemap.html] +[test_inert.html] +[test_inner_reorder.html] +[test_list.html] +[test_list_editabledoc.html] +[test_list_style.html] +[test_listbox.xhtml] +[test_menu.xhtml] +[test_menubutton.xhtml] +[test_optgroup.html] +[test_recreation.html] +[test_select.html] +[test_shadow_slots.html] +[test_shutdown.xhtml] +[test_table.html] +[test_textleaf.html] +[test_tooltip.xhtml] +[test_visibility.html] +[test_whitespace.html] diff --git a/accessible/tests/mochitest/treeupdate/test_ariadialog.html b/accessible/tests/mochitest/treeupdate/test_ariadialog.html new file mode 100644 index 0000000000..0b7f4bbb56 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_ariadialog.html @@ -0,0 +1,113 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Table creation in ARIA dialog test</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function showARIADialog(aID) { + this.node = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.node), + ]; + + this.invoke = function showARIADialog_invoke() { + getNode("dialog").style.display = "block"; + getNode("table").style.visibility = "visible"; + getNode("a").textContent = "link"; + getNode("input").value = "hello"; + getNode("input").focus(); + }; + + this.finalCheck = function showARIADialog_finalCheck() { + var tree = { + role: ROLE_DIALOG, + children: [ + { + role: ROLE_PUSHBUTTON, + children: [ { role: ROLE_TEXT_LEAF } ], + }, + { + role: ROLE_ENTRY, + }, + ], + }; + testAccessibleTree(aID, tree); + }; + + this.getID = function showARIADialog_getID() { + return "show ARIA dialog"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + // enableLogging("tree"); + gQueue = new eventQueue(); + + // make the accessible an inaccessible + gQueue.push(new showARIADialog("dialog")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="dialog" role="dialog" style="display: none;"> + <table id="table" role="presentation" + style="display: block; position: absolute; top: 88px; left: 312.5px; z-index: 10010; visibility: hidden;"> + <tbody> + <tr> + <td role="presentation"> + <div role="presentation"> + <a id="a" role="button">text</a> + </div> + <input id="input"> + </td> + </tr> + </tbody> + </table> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_ariahidden.html b/accessible/tests/mochitest/treeupdate/test_ariahidden.html new file mode 100644 index 0000000000..302465b59f --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_ariahidden.html @@ -0,0 +1,118 @@ +<html> + +<head> + <title>aria-hidden tree update tests</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function t1_setARIAHidden() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "t1"), + ]; + + this.invoke = function t1_setARIAHidden_invoke() { + getNode("t1_child").setAttribute("aria-hidden", "true"); + }; + + this.finalCheck = function t1_setARIAHidden_finalCheck() { + ok(!isAccessible("t1_child"), "No accessible for aria-hidden"); + }; + + this.getID = function t1_setARIAHidden_getID() { + return "aria-hidden set to true"; + }; + } + + function t1_removeARIAHidden() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "t1"), + ]; + + this.invoke = function t1_removeARIAHidden_invoke() { + getNode("t1_child").removeAttribute("aria-hidden"); + }; + + this.finalCheck = function t1_removeARIAHidden_finalCheck() { + ok(isAccessible("t1_child"), "No aria-hidden, has to be accessible"); + }; + + this.getID = function t1_removeARIAHidden_getID() { + return "remove aria-hidden"; + }; + } + + function t2_setARIAHidden() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "t2"), + ]; + + this.invoke = function t2_setARIAHidden_invoke() { + getNode("t2_child").setAttribute("aria-hidden", "true"); + }; + + this.finalCheck = function t2_setARIAHidden_finalCheck() { + testAccessibleTree("t2", { SECTION: []}); + }; + + this.getID = function t2_setARIAHidden_getID() { + return "t2: set aria-hidden"; + }; + } + + function t2_insertUnderARIAHidden() { + this.eventSeq = [ + new unexpectedInvokerChecker(EVENT_REORDER, "t2"), + ]; + + this.invoke = function t2_insertUnderARIAHidden_invoke() { + getNode("t2_child").innerHTML = "<input>"; + }; + + this.finalCheck = function t2_insertUnderARIAHidden_finalCheck() { + testAccessibleTree("t2", { SECTION: []}); + }; + + this.getID = function t2_insertUnderARIAHidden_getID() { + return "t2: insert under aria-hidden"; + }; + } + + // gA11yEventDumpToConsole = true; + function doTests() { + ok(!isAccessible("t1_child"), "No accessible for aria-hidden"); + + const gQueue = new eventQueue(); + gQueue.push(new t1_removeARIAHidden()); + gQueue.push(new t1_setARIAHidden()); + gQueue.push(new t2_setARIAHidden()); + gQueue.push(new t2_insertUnderARIAHidden()); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="t1"><div id="t1_child" aria-hidden="true">Hi</div><div>there</div></div> + <div id="t2"> + <span id="t2_child">hoho</span> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_ariaowns.html b/accessible/tests/mochitest/treeupdate/test_ariaowns.html new file mode 100644 index 0000000000..89d49efd5f --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html @@ -0,0 +1,851 @@ +<!DOCTYPE html> +<html> + +<head> + <title>@aria-owns attribute 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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + // ////////////////////////////////////////////////////////////////////////// + + function changeARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_button")), + // no hide for t1_subdiv because it is contained by hidden t1_checkbox + new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")), + new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")), + new invokerChecker(EVENT_SHOW, getNode("t1_button")), + new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")), + new invokerChecker(EVENT_REORDER, getNode("t1_container")), + ]; + + this.invoke = function setARIAOwns_invoke() { + // children are swapped by ARIA owns + var tree = + { SECTION: [ + { CHECKBUTTON: [ + { SECTION: [] }, + ] }, + { PUSHBUTTON: [ ] }, + ] }; + testAccessibleTree("t1_container", tree); + + getNode("t1_container"). + setAttribute("aria-owns", "t1_button t1_subdiv"); + }; + + this.finalCheck = function setARIAOwns_finalCheck() { + // children are swapped again, button and subdiv are appended to + // the children. + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // checkbox, native order + { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own + { SECTION: [ ] }, // subdiv from the subtree, ARIA owned + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function setARIAOwns_getID() { + return "Change @aria-owns attribute"; + }; + } + + function removeARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_button")), + new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), + new orderChecker(), + new asyncInvokerChecker(EVENT_SHOW, getNode("t1_button")), + new asyncInvokerChecker(EVENT_SHOW, getNode("t1_subdiv")), + new orderChecker(), + new invokerChecker(EVENT_REORDER, getNode("t1_container")), + new unexpectedInvokerChecker(EVENT_REORDER, getNode("t1_checkbox")), + ]; + + this.invoke = function removeARIAOwns_invoke() { + getNode("t1_container").removeAttribute("aria-owns"); + }; + + this.finalCheck = function removeARIAOwns_finalCheck() { + // children follow the DOM order + var tree = + { SECTION: [ + { PUSHBUTTON: [ ] }, + { CHECKBUTTON: [ + { SECTION: [] }, + ] }, + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function removeARIAOwns_getID() { + return "Remove @aria-owns attribute"; + }; + } + + function setARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_button")), + new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), + new invokerChecker(EVENT_SHOW, getNode("t1_button")), + new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")), + new invokerChecker(EVENT_REORDER, getNode("t1_container")), + ]; + + this.invoke = function setARIAOwns_invoke() { + getNode("t1_container"). + setAttribute("aria-owns", "t1_button t1_subdiv"); + }; + + this.finalCheck = function setARIAOwns_finalCheck() { + // children are swapped again, button and subdiv are appended to + // the children. + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // checkbox + { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own + { SECTION: [ ] }, // subdiv from the subtree, ARIA owned + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function setARIAOwns_getID() { + return "Set @aria-owns attribute"; + }; + } + + function addIdToARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_group")), + new invokerChecker(EVENT_SHOW, getNode("t1_group")), + new invokerChecker(EVENT_REORDER, document), + ]; + + this.invoke = function addIdToARIAOwns_invoke() { + getNode("t1_container"). + setAttribute("aria-owns", "t1_button t1_subdiv t1_group"); + }; + + this.finalCheck = function addIdToARIAOwns_finalCheck() { + // children are swapped again, button and subdiv are appended to + // the children. + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // t1_checkbox + { PUSHBUTTON: [ ] }, // button, t1_button + { SECTION: [ ] }, // subdiv from the subtree, t1_subdiv + { GROUPING: [ ] }, // group from outside, t1_group + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function addIdToARIAOwns_getID() { + return "Add id to @aria-owns attribute value"; + }; + } + + function appendEl() { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getNode, "t1_child3"), + new invokerChecker(EVENT_REORDER, getNode("t1_container")), + ]; + + this.invoke = function appendEl_invoke() { + var div = document.createElement("div"); + div.setAttribute("id", "t1_child3"); + div.setAttribute("role", "radio"); + getNode("t1_container").appendChild(div); + }; + + this.finalCheck = function appendEl_finalCheck() { + // children are invalidated, they includes aria-owns swapped kids and + // newly inserted child. + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // existing explicit, t1_checkbox + { RADIOBUTTON: [ ] }, // new explicit, t1_child3 + { PUSHBUTTON: [ ] }, // ARIA owned, t1_button + { SECTION: [ ] }, // ARIA owned, t1_subdiv + { GROUPING: [ ] }, // ARIA owned, t1_group + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function appendEl_getID() { + return "Append child under @aria-owns element"; + }; + } + + function removeEl() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), + new invokerChecker(EVENT_REORDER, getNode("t1_container")), + ]; + + this.invoke = function removeEl_invoke() { + // remove a container of t1_subdiv + getNode("t1_span").remove(); + }; + + this.finalCheck = function removeEl_finalCheck() { + // subdiv should go away + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // explicit, t1_checkbox + { RADIOBUTTON: [ ] }, // explicit, t1_child3 + { PUSHBUTTON: [ ] }, // ARIA owned, t1_button + { GROUPING: [ ] }, // ARIA owned, t1_group + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function removeEl_getID() { + return "Remove a container of ARIA owned element"; + }; + } + + function removeId() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_group")), + new invokerChecker(EVENT_SHOW, getNode("t1_group")), + new invokerChecker(EVENT_REORDER, document), + ]; + + this.invoke = function removeId_invoke() { + getNode("t1_group").removeAttribute("id"); + }; + + this.finalCheck = function removeId_finalCheck() { + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, + { RADIOBUTTON: [ ] }, + { PUSHBUTTON: [ ] }, // ARIA owned, t1_button + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function removeId_getID() { + return "Remove ID from ARIA owned element"; + }; + } + + function setId() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")), + new invokerChecker(EVENT_SHOW, getNode("t1_grouptmp")), + new invokerChecker(EVENT_REORDER, document), + ]; + + this.invoke = function setId_invoke() { + getNode("t1_grouptmp").setAttribute("id", "t1_group"); + }; + + this.finalCheck = function setId_finalCheck() { + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, + { RADIOBUTTON: [ ] }, + { PUSHBUTTON: [ ] }, // ARIA owned, t1_button + { GROUPING: [ ] }, // ARIA owned, t1_group, previously t1_grouptmp + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function setId_getID() { + return "Set ID that is referred by ARIA owns"; + }; + } + + /** + * Remove an accessible DOM element containing an element referred by + * ARIA owns. + */ + function removeA11eteiner() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("t2_container1")), + ]; + + this.invoke = function removeA11eteiner_invoke() { + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // ARIA owned, 't2_owned' + ] }; + testAccessibleTree("t2_container1", tree); + + getNode("t2_container2").removeChild(getNode("t2_container3")); + }; + + this.finalCheck = function removeA11eteiner_finalCheck() { + var tree = + { SECTION: [ + ] }; + testAccessibleTree("t2_container1", tree); + }; + + this.getID = function removeA11eteiner_getID() { + return "Remove an accessible DOM element containing an element referred by ARIA owns"; + }; + } + + /** + * Attempt to steal an element from other ARIA owns element. This should + * not be possible. The only child that will get owned into this + * container is a previously not aria-owned one. + */ + function stealFromOtherARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("t3_container3")), + ]; + + this.invoke = function stealFromOtherARIAOwns_invoke() { + getNode("t3_container3").setAttribute("aria-owns", "t3_child t3_child2"); + }; + + this.finalCheck = function stealFromOtherARIAOwns_finalCheck() { + var tree = + { SECTION: [ + { CHECKBUTTON: [ + ] }, + ] }; + testAccessibleTree("t3_container1", tree); + + tree = + { SECTION: [ + ] }; + testAccessibleTree("t3_container2", tree); + + tree = + { SECTION: [ + { CHECKBUTTON: [ + ] }, + ] }; + testAccessibleTree("t3_container3", tree); + }; + + this.getID = function stealFromOtherARIAOwns_getID() { + return "Steal an element from other ARIA owns element"; + }; + } + + function appendElToRecacheChildren() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("t3_container3")), + ]; + + this.invoke = function appendElToRecacheChildren_invoke() { + var div = document.createElement("div"); + div.setAttribute("role", "radio"); + getNode("t3_container3").appendChild(div); + }; + + this.finalCheck = function appendElToRecacheChildren_finalCheck() { + var tree = + { SECTION: [ + ] }; + testAccessibleTree("t3_container2", tree); + + tree = + { SECTION: [ + { RADIOBUTTON: [ ] }, + { CHECKBUTTON: [ ] }, // ARIA owned + ] }; + testAccessibleTree("t3_container3", tree); + }; + + this.getID = function appendElToRecacheChildren_getID() { + return "Append a child under @aria-owns element to trigger children recache"; + }; + } + + function showHiddenElement() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("t4_container1")), + ]; + + this.invoke = function showHiddenElement_invoke() { + var tree = + { SECTION: [ + { RADIOBUTTON: [] }, + ] }; + testAccessibleTree("t4_container1", tree); + + getNode("t4_child1").style.display = "block"; + }; + + this.finalCheck = function showHiddenElement_finalCheck() { + var tree = + { SECTION: [ + { CHECKBUTTON: [] }, + { RADIOBUTTON: [] }, + ] }; + testAccessibleTree("t4_container1", tree); + }; + + this.getID = function showHiddenElement_getID() { + return "Show hidden ARIA owns referred element"; + }; + } + + function rearrangeARIAOwns(aContainer, aAttr, aIdList, aRoleList) { + this.eventSeq = []; + for (let id of aIdList) { + this.eventSeq.push(new invokerChecker(EVENT_HIDE, getNode(id))); + } + + for (let id of aIdList) { + this.eventSeq.push(new invokerChecker(EVENT_SHOW, getNode(id))); + } + this.eventSeq.push(new invokerChecker(EVENT_REORDER, getNode(aContainer))); + + this.invoke = function rearrangeARIAOwns_invoke() { + getNode(aContainer).setAttribute("aria-owns", aAttr); + }; + + this.finalCheck = function rearrangeARIAOwns_finalCheck() { + var tree = { SECTION: [ ] }; + for (var role of aRoleList) { + var ch = {}; + ch[role] = []; + tree.SECTION.push(ch); + } + testAccessibleTree(aContainer, tree); + }; + + this.getID = function rearrangeARIAOwns_getID() { + return `Rearrange @aria-owns attribute to '${aAttr}'`; + }; + } + + function removeNotARIAOwnedEl(aContainer, aChild) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aContainer), + ]; + + this.invoke = function removeNotARIAOwnedEl_invoke() { + var tree = { + SECTION: [ + { TEXT_LEAF: [ ] }, + { GROUPING: [ ] }, + ], + }; + testAccessibleTree(aContainer, tree); + + getNode(aContainer).removeChild(getNode(aChild)); + }; + + this.finalCheck = function removeNotARIAOwnedEl_finalCheck() { + var tree = { + SECTION: [ + { GROUPING: [ ] }, + ], + }; + testAccessibleTree(aContainer, tree); + }; + + this.getID = function removeNotARIAOwnedEl_getID() { + return `remove not ARIA owned child`; + }; + } + + function setARIAOwnsOnElToRemove(aParent, aChild) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getAccessible(aParent)), + ]; + + this.invoke = function setARIAOwnsOnElToRemove_invoke() { + getNode(aChild).setAttribute("aria-owns", "no_id"); + getNode(aParent).removeChild(getNode(aChild)); + getNode(aParent).remove(); + }; + + this.getID = function setARIAOwnsOnElToRemove_getID() { + return `set ARIA owns on an element, and then remove it, and then remove its parent`; + }; + } + + /** + * Set ARIA owns on inaccessible span element that contains + * accessible children. This will move children from the container for + * the span. + */ + function test8() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "t8_container"), + ]; + + this.invoke = function test8_invoke() { + var tree = + { SECTION: [ + { PUSHBUTTON: [] }, + { ENTRY: [] }, + { ENTRY: [] }, + { ENTRY: [] }, + ] }; + testAccessibleTree("t8_container", tree); + + getNode(t8_container).setAttribute("aria-owns", "t8_span t8_button"); + }; + + this.finalCheck = function test8_finalCheck() { + var tree = + { SECTION: [ + { TEXT: [ + { ENTRY: [] }, + { ENTRY: [] }, + { ENTRY: [] }, + ] }, + { PUSHBUTTON: [] }, + ] }; + testAccessibleTree("t8_container", tree); + }; + + this.getID = function test8_getID() { + return `Set ARIA owns on inaccessible span element that contains accessible children`; + }; + } + + function test9_prepare() { + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, () => { + return getNode("t9_container").contentDocument; + }), + ]; + + this.invoke = () => { + // The \ before the final /script avoids the script from being terminated + // by the html parser. + getNode("t9_container").src = `data:text/html, + <html><body></body> + <script> + let el = document.createElement('div'); + el.id = 'container'; + el.innerHTML = "<input id='input'>"; + document.documentElement.appendChild(el); + <\/script></html>`; + }; + + this.finalCheck = () => { + var tree = + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { SECTION: [ + { ENTRY: [] }, + ] }, + ] }, + ] }; + testAccessibleTree("t9_container", tree); + }; + + this.getID = () => { + return `Set ARIA owns on a document (part1)`; + }; + } + + function test9_setARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, () => { + let doc = getNode("t9_container").contentDocument; + return doc && doc.getElementById("input"); + }), + ]; + + this.invoke = () => { + let doc = getNode("t9_container").contentDocument; + doc.body.setAttribute("aria-owns", "input"); + }; + + this.finalCheck = () => { + var tree = + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { SECTION: [] }, + { ENTRY: [] }, + ] }, + ] }; + testAccessibleTree("t9_container", tree); + }; + + this.getID = () => { + return `Set ARIA owns on a document (part2)`; + }; + } + + function test9_finish() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, () => { + return getNode("t9_container").contentDocument; + }), + ]; + + this.invoke = () => { + // trigger a tree update. + let doc = getNode("t9_container").contentDocument; + doc.body.appendChild(doc.createElement("p")); + }; + + this.finalCheck = () => { + var tree = + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { PARAGRAPH: [] }, + { SECTION: [] }, + { ENTRY: [] }, + ] }, + ] }; + testAccessibleTree("t9_container", tree); + }; + + this.getID = () => { + return `Set ARIA owns on a document (part3)`; + }; + } + + /** + * Put ARIA owned child back when ARIA owner removed. + */ + function test10_removeARIAOwner() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getAccessible("t10_owner")), + ]; + + this.invoke = () => { + let tree = + { SECTION: [ // t10_container + { SECTION: [ // t10_owner + { ENTRY: [] }, // t10_child + ] }, + ] }; + testAccessibleTree("t10_container", tree); + + getNode("t10_owner").remove(); + }; + + this.getID = () => { + return "Put aria owned child back when aria owner removed"; + }; + } + + function test10_finishTest() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "t10_container"), + ]; + + this.invoke = () => { + // trigger a tree update. + getNode("t10_container").append(document.createElement("p")); + }; + + this.finalCheck = () => { + let tree = + { SECTION: [ // t10_container + // { ENTRY: [] }, // t10_child + { PARAGRAPH: [] }, + ] }; + testAccessibleTree("t10_container", tree); + todo(false, "Input accessible has be moved back in the tree"); + }; + + this.getID = () => { + return `Put aria owned child back when aria owner removed (finish test)`; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + // ////////////////////////////////////////////////////////////////////////// + + // gA11yEventDumpToConsole = true; + // enableLogging("tree,eventTree,verbose"); // debug stuff + + var gQueue = null; + + async function doTest() { + let PromEvents = {}; + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js", + PromEvents); + + gQueue = new eventQueue(); + let queueFinished = new Promise(resolve => { + gQueue.onFinish = function() { + resolve(); + return DO_NOT_FINISH_TEST; + }; + }); + + // test1 + gQueue.push(new changeARIAOwns()); + gQueue.push(new removeARIAOwns()); + gQueue.push(new setARIAOwns()); + gQueue.push(new addIdToARIAOwns()); + gQueue.push(new appendEl()); + gQueue.push(new removeEl()); + gQueue.push(new removeId()); + gQueue.push(new setId()); + + // test2 + gQueue.push(new removeA11eteiner()); + + // test3 + gQueue.push(new stealFromOtherARIAOwns()); + gQueue.push(new appendElToRecacheChildren()); + + // test4 + gQueue.push(new showHiddenElement()); + + // test5 + gQueue.push(new rearrangeARIAOwns( + "t5_container", "t5_checkbox t5_radio t5_button", + [ "t5_checkbox", "t5_radio", "t5_button" ], + [ "CHECKBUTTON", "RADIOBUTTON", "PUSHBUTTON" ])); + gQueue.push(new rearrangeARIAOwns( + "t5_container", "t5_radio t5_button t5_checkbox", + [ "t5_radio", "t5_button" ], + [ "RADIOBUTTON", "PUSHBUTTON", "CHECKBUTTON" ])); + + gQueue.push(new removeNotARIAOwnedEl("t6_container", "t6_span")); + + gQueue.push(new setARIAOwnsOnElToRemove("t7_parent", "t7_child")); + + gQueue.push(new test8()); + gQueue.push(new test9_prepare()); + gQueue.push(new test9_setARIAOwns()); + gQueue.push(new test9_finish()); + + gQueue.push(new test10_removeARIAOwner()); + gQueue.push(new test10_finishTest()); + + gQueue.invoke(); + await queueFinished; + + let owned = document.createElement('div'); + owned.id = 't11_child'; + owned.textContent = 'owned'; + let evtPromise = PromEvents.waitForEvent(EVENT_SHOW, "t11_child"); + getNode("t11_container").append(owned); + let evt = await evtPromise; + is(evt.accessible.parent.name, "t11_owner"); + + // Test owning an ancestor which isn't created yet. + testAccessibleTree("t12_container", { SECTION: [ // t12_container + { SECTION: [ // t12b + { SECTION: [] }, // t12c + ] }, + { SECTION: [] }, // t12d + ] }); + // Owning t12a would create a cycle, so we expect it to do nothing. + // We own t12d so we get an event when aria-owns relocation is complete. + evtPromise = PromEvents.waitForEvent(EVENT_SHOW, "t12d"); + getNode("t12c").setAttribute("aria-owns", "t12a t12d"); + await evtPromise; + testAccessibleTree("t12_container", { SECTION: [ // t12_container + { SECTION: [ // t12b + { SECTION: [ // t12c + { SECTION: [] }, // t12d + ] }, + ] }, + ] }); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + + </script> +</head> + +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="t1_container" aria-owns="t1_checkbox t1_button"> + <div role="button" id="t1_button"></div> + <div role="checkbox" id="t1_checkbox"> + <span id="t1_span"> + <div id="t1_subdiv"></div> + </span> + </div> + </div> + <div id="t1_group" role="group"></div> + <div id="t1_grouptmp" role="group"></div> + + <div id="t2_container1" aria-owns="t2_owned"></div> + <div id="t2_container2"> + <div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div> + </div> + + <div id="t3_container1" aria-owns="t3_child"></div> + <div id="t3_child" role="checkbox"></div> + <div id="t3_container2"> + <div id="t3_child2" role="checkbox"></div> + </div> + <div id="t3_container3"></div> + + <div id="t4_container1" aria-owns="t4_child1 t4_child2"></div> + <div id="t4_container2"> + <div id="t4_child1" style="display:none" role="checkbox"></div> + <div id="t4_child2" role="radio"></div> + </div> + + <div id="t5_container"> + <div role="button" id="t5_button"></div> + <div role="checkbox" id="t5_checkbox"></div> + <div role="radio" id="t5_radio"></div> + </div> + + <div id="t6_container" aria-owns="t6_fake"> + <span id="t6_span">hey</span> + </div> + <div id="t6_fake" role="group"></div> + + <div id="t7_container"> + <div id="t7_parent"> + <div id="t7_child"></div> + </div> + </div> + + <div id="t8_container"> + <input id="t8_button" type="button"><span id="t8_span"><input><input><input></span> + </div> + + <iframe id="t9_container"></iframe> + + <div id="t10_container"> + <div id="t10_owner" aria-owns="t10_child"></div> + <input id="t10_child"> + </div> + + <div id="t11_container" aria-label="t11_container"> + <div aria-owns="t11_child" aria-label="t11_owner"></div> + </div> + + <div id="t12_container"> + <span id="t12a"> + <div id="t12b" aria-owns="t12c"></div> + </span> + <div id="t12c"></div> + <div id="t12d"></div> + </div> +</body> + +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug1040735.html b/accessible/tests/mochitest/treeupdate/test_bug1040735.html new file mode 100644 index 0000000000..b7d0e472d0 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1040735.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<head> + <title>Adopt DOM node from anonymous subtree</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"> + function doTest() { + document.body.appendChild(document.getElementById("mw_a")); + setTimeout(function() { ok(true, "no crash and assertions"); SimpleTest.finish(); }, 0); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1040735" + title="Bug 1040735 - DOM node reinsertion under anonymous content may trigger a11y child adoption"> + Bug 1040735</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <marquee> + <div id="mw_a" style="visibility: hidden;"> + <div style="visibility: visible;" id="mw_inside"></div> + </div> + </marquee> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug1175913.html b/accessible/tests/mochitest/treeupdate/test_bug1175913.html new file mode 100644 index 0000000000..1fe2720434 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1175913.html @@ -0,0 +1,95 @@ +<html> + +<head> + <title>Test hide/show events on event listener changes</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + function dummyListener() {} + + function testAddListener() { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getNode("parent")), + ]; + + this.invoke = function testAddListener_invoke() { + is(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that parent is not accessible."); + is(getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that child is not accessible."); + getNode("parent").addEventListener("click", dummyListener); + }; + + this.finalCheck = function testAddListener_finalCheck() { + var tree = { TEXT: [] }; + testAccessibleTree("parent", tree); + }; + + this.getID = function testAddListener_getID() { + return "Test that show event is sent when click listener is added"; + }; + } + + function testRemoveListener() { + this.eventSeq = [ + new unexpectedInvokerChecker(EVENT_HIDE, getNode("parent")), + ]; + + this.invoke = function testRemoveListener_invoke() { + getNode("parent").removeEventListener("click", dummyListener); + }; + + this.finalCheck = function testRemoveListener_finalCheck() { + ok(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC), + "Parent stays accessible after click event listener is removed"); + ok(!getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC), + "Child stays inaccessible"); + }; + + this.getID = function testRemoveListener_getID() { + return "Test that hide event is sent when click listener is removed"; + }; + } + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + gQueue.push(new testAddListener()); + gQueue.push(new testRemoveListener()); + gQueue.invoke(); // SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175913" + title="Crash in mozilla::a11y::DocAccessibleParent::RemoveAccessible(ProxyAccessible* aAccessible)"> + Mozilla Bug 1175913 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <span id="parent"> + <span id="child"> + </span> + </span> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug1189277.html b/accessible/tests/mochitest/treeupdate/test_bug1189277.html new file mode 100644 index 0000000000..95efe5135a --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1189277.html @@ -0,0 +1,82 @@ +<html> + +<head> + <title>Test hide/show events for HTMLListBulletAccessibles on list restyle</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="../name.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function runTest() { + this.containerNode = getNode("outerDiv"); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("child")), + new invokerChecker(EVENT_HIDE, getNode("childDoc")), + new invokerChecker(EVENT_SHOW, "newChildDoc"), + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function runTest_invoke() { + this.containerNode.removeChild(getNode("child")); + + var docContainer = getNode("docContainer"); + var iframe = document.createElement("iframe"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + iframe.setAttribute("src", "http://example.com"); + iframe.setAttribute("id", "newChildDoc"); + + docContainer.removeChild(getNode("childDoc")); + docContainer.appendChild(iframe); + }; + + this.getID = function runTest_getID() { + return "check show events are not incorrectly coalesced"; + }; + } + + // enableLogging("tree"); + gA11yEventDumpToConsole = true; + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + gQueue.push(new runTest()); + gQueue.invoke(); // SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1189277" + title="content process crash caused by missing show event"> + Mozilla Bug 1189277 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="outerDiv"> + <div id="child">foo</div> + <div id="docContainer"> + <iframe id="childDoc" src="about:blank"> + </iframe> + </div> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug1276857.html b/accessible/tests/mochitest/treeupdate/test_bug1276857.html new file mode 100644 index 0000000000..a164247534 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1276857.html @@ -0,0 +1,131 @@ +<!DOCTYPE html> +<html> + +<head> + <title>DOM mutations test</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function runTest() { + let iframe = document.getElementById("iframe"); + + // children change will recreate the table + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, () => { + let doc = getNode("iframe").contentDocument; + return doc && doc.getElementById("c1"); + }), + ]; + + this.invoke = function runTest_invoke() { + var tree = { + SECTION: [ // c1 + { TEXT_LEAF: [] }, // Some text + { TEXT_CONTAINER: [ + { TEXT_LEAF: [] }, // something with .. + ] }, + { TEXT_LEAF: [] }, // More text + ], + }; + testAccessibleTree(iframe.contentDocument.getElementById("c1"), tree); + + iframe.contentDocument.getElementById("c1_t").querySelector("span").remove(); + }; + + this.finalCheck = function runTest_finalCheck() { + var tree = { + SECTION: [ // c1 + { TEXT_LEAF: [] }, // Some text + { TEXT_LEAF: [] }, // More text + ], + }; + testAccessibleTree(iframe.contentDocument.getElementById("c1"), tree); + }; + + this.getID = function runTest_getID() { + return "child DOM node is removed before the layout notifies the a11y about parent removal/show"; + }; + } + + function runShadowTest() { + // children change will recreate the table + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, () => { + let doc = getNode("iframe").contentDocument; + return doc && doc.getElementById("c2"); + }), + ]; + + this.invoke = function runShadowTest_invoke() { + var tree = { + SECTION: [ // c2 + { TEXT_LEAF: [] }, // Some text + { TEXT_CONTAINER: [ + { TEXT_LEAF: [] }, // something with .. + ] }, + { TEXT_LEAF: [] }, // More text + ], + }; + const iframe = document.getElementById("iframe"); + testAccessibleTree(iframe.contentDocument.getElementById("c2"), tree); + + var shadowRoot = iframe.contentDocument.getElementById("c2_c").shadowRoot; + shadowRoot.firstElementChild.querySelector("span").remove(); + // bug 1487312 + shadowRoot.firstElementChild.offsetTop; + shadowRoot.appendChild(document.createElement("button")); + }; + + this.finalCheck = function runShadowTest_finalCheck() { + var tree = { + SECTION: [ // c2 + { TEXT_LEAF: [] }, // Some text + { TEXT_LEAF: [] }, // More text + { PUSHBUTTON: [] }, // The button we appended. + ], + }; + const iframe = document.getElementById("iframe"); + testAccessibleTree(iframe.contentDocument.getElementById("c2"), tree); + }; + + this.getID = function runShadowTest_getID() { + return "child DOM node is removed before the layout notifies the a11y about parent removal/show in shadow DOM"; + }; + } + + // enableLogging("tree"); + // gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + gQueue.push(new runTest()); + gQueue.push(new runShadowTest()); + gQueue.invoke(); // will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + window.onload = () => { + let iframe = document.createElement("iframe"); + iframe.id = "iframe"; + iframe.src = "test_bug1276857_subframe.html"; + addA11yLoadEvent(doTest, iframe.contentWindow); + document.body.appendChild(iframe); + }; + </script> + +</head> +<body> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html b/accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html new file mode 100644 index 0000000000..869c9ebe6c --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>DOM mutations test</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="application/javascript" src="../role.js"></script> +</head> +<body> + <div id="c1"> + <div id="c1_t" style="display: table" role="presentation"> + Some text + <span style="display: table-cell">something with accessibles goes here</span> + More text + </div> + </div> + + <template id="tmpl"> + <div style="display: table" role="presentation"> + Some text + <span style="display: table-cell">something with accessibles goes here</span> + More text + </div> + </template> + + <div id="c2"><div id="c2_c" role="presentation"></div></div> + + <script> + var gShadowRoot = document.getElementById("c2_c").attachShadow({mode: "open"}); + var tmpl = document.getElementById("tmpl"); + gShadowRoot.appendChild(document.importNode(tmpl.content, true)); + </script> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml b/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml new file mode 100644 index 0000000000..51a3c39047 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml @@ -0,0 +1,57 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Canvas subdom mutation</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="../role.js"></script> + + <script> + <![CDATA[ + function doTest() { + var the_displayNone = getNode("the_displaynone"); + var the_table = getNode("the_table"); + var the_row = getNode("the_row"); + ok(isAccessible(the_table), "table should be accessible"); + the_displayNone.appendChild(the_table); + ok(!isAccessible(the_table), "table in display none tree shouldn't be accessible"); + + setTimeout(function() { + document.body.removeChild(the_row); + // make sure no accessibles have stuck around. + ok(!isAccessible(the_row), "row shouldn't be accessible"); + ok(!isAccessible(the_table), "table shouldn't be accessible"); + ok(!isAccessible(the_displayNone), "display none things shouldn't be accessible"); + SimpleTest.finish(); + }, 0); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> +</head> +<body> + + <a target="_blank" + title="test accessible removal when reframe root isn't accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=852150"> + Mozilla Bug 852150 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="the_displaynone" style="display: none;"></div> + <table id="the_table"></table> + <tr id="the_row"></tr> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml b/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml new file mode 100644 index 0000000000..5d9e813f3a --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml @@ -0,0 +1,31 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> + +function boom() { + var newSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "span"); + document.getElementById("c").insertBefore(newSpan, document.getElementById("d")); + document.getElementById("a").style.visibility = "visible"; + ok(true, "test didn't crash or assert"); + SimpleTest.finish(); +} + +</script> +</head> + +<body onload="boom();"> + <a target="_blank" + title="test reparenting accessible subtree when inaccessible element becomes accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=883708"> + Mozilla Bug 883708 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + +<div style="visibility: collapse;" id="a"><div style="float: right; visibility: visible;"><div id="c"><td id="d"></td></div></div></div></body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml b/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml new file mode 100644 index 0000000000..7e3cf14fac --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml @@ -0,0 +1,19 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> + +function boom() { + document.getElementById("k").removeAttribute("href"); + ok(true, "changing iframe contents doesn't cause assertions"); + SimpleTest.finish(); +} + +</script> +</head> + +<body onload="boom();"> +<iframe src="data:text/html,1"><link id="k" href="data:text/html,2" /></iframe> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug895082.html b/accessible/tests/mochitest/treeupdate/test_bug895082.html new file mode 100644 index 0000000000..8332c5206e --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug895082.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<head> +<title>Replace body test</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> +function doTest() { + var y = document.getElementById("y"); + var oldBody = document.body; + var newBody = document.createElement("body"); + document.documentElement.insertBefore(newBody, oldBody); + setTimeout(function() { + document.documentElement.removeChild(oldBody); + newBody.appendChild(y); + ok(true, "we didn't assert"); + SimpleTest.finish(); + }, 0); +} + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=895082" + title="Bug 895082 - replacing body element asserts"> + Bug 895082</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + +<div><div id="y"></div></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_canvas.html b/accessible/tests/mochitest/treeupdate/test_canvas.html new file mode 100644 index 0000000000..229bf4f2e3 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_canvas.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Canvas subdom mutation</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function addSubtree(aID) { + this.node = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.node), + ]; + + this.invoke = function addSubtree_invoke() { + // ensure we start with no subtree + testAccessibleTree("canvas", { CANVAS: [] }); + getNode("dialog").style.display = "block"; + }; + + this.finalCheck = function addSubtree_finalCheck() { + testAccessibleTree("dialog", { DIALOG: [] }); + }; + + this.getID = function addSubtree_getID() { + return "show canvas subdom"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + gQueue = new eventQueue(); + + // make the subdom come alive! + gQueue.push(new addSubtree("dialog")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Expose content in Canvas element" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912"> + Mozilla Bug 495912 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <canvas id="canvas"> + <div id="dialog" role="dialog" style="display: none;"> + </div> + </canvas> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml b/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml new file mode 100644 index 0000000000..f81d77332d --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml @@ -0,0 +1,315 @@ +<?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 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="menu tree and events"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../role.js" /> + + <script type="application/javascript"> + <![CDATA[ + + function openMenu(aID, aTree) + { + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_START, getNode(aID)) + ]; + + this.invoke = function openMenu_invoke() + { + var button = getNode("button"); + getNode(aID).openPopup(button, "after_start", 0, 0, true, false); + } + + this.finalCheck = function openMenu_finalCheck(aEvent) + { + testAccessibleTree(aID, aTree); + } + + this.getID = function openMenu_getID() + { + return "open menu " + prettyName(aID); + } + } + + function selectNextMenuItem(aID) + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getNode(aID)) + ]; + + this.invoke = function selectMenuItem_invoke() + { + synthesizeKey("KEY_ArrowDown"); + } + + this.getID = function selectMenuItem_getID() + { + return "select menuitem " + prettyName(aID); + } + } + + function openSubMenu(aSubMenuID, aItemID, aMenuID, aTree) + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getNode(aItemID)), + ]; + + this.invoke = function openSubMenu_invoke() + { + synthesizeKey("KEY_Enter"); + } + + this.finalCheck = function openSubMenu_finalCheck(aEvent) + { + testAccessibleTree(aMenuID, aTree); + } + + this.getID = function openSubMenu_getID() + { + return "open submenu " + prettyName(aSubMenuID) + " focusing item " + prettyName(aItemID); + } + } + + function closeSubMenu(aSubMenuID, aItemID) + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getNode(aItemID)), + ]; + + this.invoke = function closeSubMenu_invoke() + { + synthesizeKey("KEY_Escape"); + } + + this.getID = function closeSubMenu_getID() + { + return "close submenu " + prettyName(aSubMenuID) + " focusing item " + prettyName(aItemID); + } + } + + function closeMenu(aID) + { + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_END, getNode(aID)) + ]; + + this.invoke = function closeMenu_invoke() + { + synthesizeKey("KEY_Escape"); + } + + this.getID = function closeMenu_getID() + { + return "close menu " + prettyName(aID); + } + } + + //gA11yEventDumpToConsole = true; + //enableLogging("tree,verbose"); + + var gQueue = null; + var gContextTree = {}; + + // Linux and Windows menu trees discrepancy: bug 527646. + + /** + * Return the context menu tree before submenus were open. + */ + function getMenuTree1() + { + if (LINUX || SOLARIS) { + let tree = { + role: ROLE_MENUPOPUP, + children: [ + { + name: "item0", + role: ROLE_MENUITEM, + children: [] + }, + { + name: "item1", + role: ROLE_MENUITEM, + children: [] + }, + { + name: "item2", + role: ROLE_PARENT_MENUITEM, + children: [ ] + } + ] + }; + return tree; + } + + // Windows + let tree = { + role: ROLE_MENUPOPUP, + children: [ + { + name: "item0", + role: ROLE_MENUITEM, + children: [] + }, + { + name: "item1", + role: ROLE_MENUITEM, + children: [] + }, + { + name: "item2", + role: ROLE_PARENT_MENUITEM, + children: [ + { + name: "item2", + role: ROLE_MENUPOPUP, + children: [ ] + } + ] + } + ] + }; + return tree; + } + + /** + * Return context menu tree when submenu was open. + */ + function getMenuTree2() + { + var tree = getMenuTree1(); + if (LINUX || SOLARIS) { + let submenuTree = + { + name: "item2.0", + role: ROLE_PARENT_MENUITEM, + children: [ ] + }; + tree.children[2].children.push(submenuTree); + return tree; + } + + // Windows + let submenuTree = + { + name: "item2.0", + role: ROLE_PARENT_MENUITEM, + children: [ + { + name: "item2.0", + role: ROLE_MENUPOPUP, + children: [ ] + } + ] + }; + + tree.children[2].children[0].children.push(submenuTree); + return tree; + } + + /** + * Return context menu tree when subsub menu was open. + */ + function getMenuTree3() + { + var tree = getMenuTree2(); + var subsubmenuTree = + { + name: "item2.0.0", + role: ROLE_MENUITEM, + children: [] + }; + + if (LINUX || SOLARIS) + tree.children[2].children[0].children.push(subsubmenuTree); + else + tree.children[2].children[0].children[0].children[0].children.push(subsubmenuTree); + + return tree; + } + + + function doTests() + { + gQueue = new eventQueue(); + + // Check initial empty tree + testAccessibleTree("context", { MENUPOPUP: [] }); + + // Open context menu and check that menu item accesibles are created. + gQueue.push(new openMenu("context", getMenuTree1())); + + // Select items and check focus event on them. + gQueue.push(new selectNextMenuItem("item0")); + gQueue.push(new selectNextMenuItem("item1")); + gQueue.push(new selectNextMenuItem("item2")); + + // Open sub menu and check menu accessible tree and focus event. + gQueue.push(new openSubMenu("submenu2", "item2.0", + "context", getMenuTree2())); + gQueue.push(new openSubMenu("submenu2.0", "item2.0.0", + "context", getMenuTree3())); + + // Close submenus and check that focus goes to parent. + gQueue.push(new closeSubMenu("submenu2.0", "item2.0")); + gQueue.push(new closeSubMenu("submenu2", "item2")); + + gQueue.push(new closeMenu("context")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=630194" + title="Update accessible tree when opening the menu popup"> + Mozilla Bug 630194 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486" + title="Don't force accessible creation for popup children."> + Mozilla Bug 630486 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <menupopup id="context"> + <menuitem id="item0" label="item0"/> + <menuitem id="item1" label="item1"/> + <menu id="item2" label="item2"> + <menupopup id="submenu2"> + <menu id="item2.0" label="item2.0"> + <menupopup id="submenu2.0"> + <menuitem id="item2.0.0" label="item2.0.0"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> + + <button context="context" id="button">btn</button> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/treeupdate/test_cssoverflow.html b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html new file mode 100644 index 0000000000..6b60fce975 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html @@ -0,0 +1,149 @@ +<html> + +<head> + <title>Testing HTML scrollable frames (css overflow style)</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="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + // ////////////////////////////////////////////////////////////////////////// + + /** + * Change scroll range to not empty size and inserts a child into container + * to trigger tree update of the container. Prior to bug 677154 not empty + * size resulted to accessible creation for scroll area, container tree + * update picked up that accessible unattaching scroll area accessible + * subtree. + */ + function changeScrollRange(aContainerID, aScrollAreaID) { + this.containerNode = getNode(aContainerID); + this.container = getAccessible(this.containerNode); + this.scrollAreaNode = getNode(aScrollAreaID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function changeScrollRange_invoke() { + this.scrollAreaNode.style.width = "20px"; + this.containerNode.appendChild(document.createElement("input")); + }; + + this.finalCheck = function changeScrollRange_finalCheck() { + var accTree = + { SECTION: [ // container + { SECTION: [ // scroll area + { ENTRY: [] }, // child content + ] }, + { ENTRY: [] }, // inserted input + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function changeScrollRange_getID() { + return "change scroll range for " + prettyName(aScrollAreaID); + }; + } + + /** + * Change scrollbar styles from visible to auto to make the scroll area focusable. + * That causes us to create an accessible for it. + * Make sure the tree stays intact. + * The scroll area has no ID on purpose to make it inaccessible initially. + */ + function makeFocusableByScrollbarStyles(aContainerID) { + this.container = getAccessible(aContainerID); + this.scrollAreaNode = getNode(aContainerID).firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getAccessible, this.scrollAreaNode), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function makeFocusableByScrollbarStyles_invoke() { + var accTree = + { SECTION: [ // container + { PARAGRAPH: [ // paragraph + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree(this.container, accTree); + + this.scrollAreaNode.style.overflow = "auto"; + }; + + this.finalCheck = function makeFocusableByScrollbarStyles_finalCheck() { + var accTree = + { SECTION: [ // container + { role: ROLE_SECTION, // focusable scroll area + states: STATE_FOCUSABLE, + children: [ + { PARAGRAPH: [ // paragraph + { TEXT_LEAF: [] }, // text leaf + ] }, + ], + }, // focusable scroll area + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function makeFocusableByScrollbarStyles_getID() { + return "make div focusable through scrollbar styles " + + prettyName(aContainerID); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + // ////////////////////////////////////////////////////////////////////////// + + var gQueue = null; + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new changeScrollRange("container", "scrollarea")); + gQueue.push(new makeFocusableByScrollbarStyles("container2")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=677154" + title="Detached document accessibility tree"> + Mozilla Bug 677154</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div id="container"><div id="scrollarea" style="overflow:auto;"><input></div></div> + <div id="container2"><div style="height: 1px;"><p>foo</p></div></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_deck.xhtml b/accessible/tests/mochitest/treeupdate/test_deck.xhtml new file mode 100644 index 0000000000..979996a66c --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_deck.xhtml @@ -0,0 +1,154 @@ +<?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 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Tree update on XUL deck panel switching"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + function switchDeckPanel(aContainerID, aDeckID) + { + this.panelIndex = 0; + + this.container = getAccessible(aContainerID); + this.deckNode = getNode(aDeckID); + this.prevPanel = getAccessible(this.deckNode.selectedPanel); + this.panelNode = this.deckNode.childNodes[this.panelIndex]; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.prevPanel), + new invokerChecker(EVENT_SHOW, this.panelNode), + new invokerChecker(EVENT_REORDER, this.container) + ]; + + this.invoke = function switchDeckPanel_invoke() + { + var tree = + { GROUPING: [ // role="group" + { GROUPING: [ // groupbox, a selected panel #2 + { PUSHBUTTON: [ ] } // button + ] } + ] }; + testAccessibleTree(this.container, tree); + + this.deckNode.selectedIndex = this.panelIndex; + } + + this.finalCheck = function switchDeckPanel_finalCheck() + { + var tree = + { GROUPING: [ // role="group" + { LABEL: [ // description, a selected panel #1 + { TEXT_LEAF: [] } // text leaf, a description value + ] } + ] }; + testAccessibleTree(this.container, tree); + } + + this.getID = function switchDeckPanel_getID() + { + return "switch deck panel"; + } + } + + function showDeckPanel(aContainerID, aPanelID) + { + this.container = getAccessible(aContainerID); + this.deckNode = getNode(aPanelID); + var tree = + { GROUPING: [ // role="group" + { GROUPING: [ // grouping of panel 2 + { PUSHBUTTON: [] } // push button in panel 2 + ] } + ] }; + + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_REORDER, this.container) + ]; + + this.invoke = function showDeckPanel_invoke() + { + // This stops the refreh driver from doing its regular ticks, and leaves + // us in control. 100 is an arbitrary positive number to advance the clock + // it is not checked or used anywhere. + window.windowUtils.advanceTimeAndRefresh(100); + + testAccessibleTree(this.container, tree); + this.deckNode.style.display = "-moz-box"; + + // This flushes our DOM mutations and forces any pending mutation events. + window.windowUtils.advanceTimeAndRefresh(100); + } + + this.finalCheck = function showDeckPanel_finalCheck() + { + testAccessibleTree(this.container, tree); + + // Return to regular refresh driver ticks. + window.windowUtils.restoreNormalRefresh(); + } + + this.getID = function showDeckPanel_getID() + { + return "show deck panel"; + } + } + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + gQueue.push(new showDeckPanel("container", "hidden")); + gQueue.push(new switchDeckPanel("container", "deck")); + gQueue.invoke(); // will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=814836" + title=" xul:deck element messes up screen reader"> + Mozilla Bug 814836 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1" id="container" role="group"> + + <deck id="deck" selectedIndex="1"> + <description>This is the first page</description> + <groupbox> + <button label="This is the second page"/> + </groupbox> + <hbox id="hidden" style="display: none;"><label>This is the third page</label></hbox> + </deck> + + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_delayed_removal.html b/accessible/tests/mochitest/treeupdate/test_delayed_removal.html new file mode 100644 index 0000000000..3f421f0c5b --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_delayed_removal.html @@ -0,0 +1,500 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible delayed removal</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + .gentext:before { + content: "START" + } + .gentext:after { + content: "END" + } + </style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + + async function hideDivFromInsideSpan() { + let msg = "hideDivFromInsideSpan"; + info(msg); + let events = waitForOrderedEvents([ + [EVENT_HIDE, "div1"], [EVENT_TEXT_REMOVED, "span1"], + [EVENT_REORDER, "span1"] + ], msg); + document.body.offsetTop; // Flush layout. + getNode("div1").style.display = "none"; + await events; + + testAccessibleTree("c1", { SECTION: [ { REGION: [] }, ] }); + } + + async function showDivFromInsideSpan() { + let msg = "showDivFromInsideSpan"; + info(msg); + let events = waitForOrderedEvents( + [[EVENT_SHOW, "div2"], [EVENT_REORDER, "span2"]], msg); + document.body.offsetTop; // Flush layout. + getNode("div2").style.display = "block"; + await events; + + testAccessibleTree("c2", + { SECTION: [ { REGION: [{ SECTION: [ { TEXT_LEAF: [] } ] }] }, ] }); + } + + async function removeDivFromInsideSpan() { + let msg = "removeDivFromInsideSpan"; + info(msg); + let events = waitForOrderedEvents([ + [EVENT_HIDE, getNode("div3")], [EVENT_TEXT_REMOVED, "span3"], + [EVENT_REORDER, "span3"] + ], msg); + document.body.offsetTop; // Flush layout. + getNode("div3").remove(); + await events; + + testAccessibleTree("c3", { SECTION: [ { REGION: [] }, ] }); + } + + // Test to see that generated content is inserted + async function addCSSGeneratedContent() { + let msg = "addCSSGeneratedContent"; + let c4_child = getAccessible("c4_child"); + info(msg); + let events = waitForOrderedEvents([ + [EVENT_SHOW, evt => evt.accessible == c4_child.firstChild], + [EVENT_SHOW, evt => evt.accessible == c4_child.lastChild], + [EVENT_REORDER, c4_child]], msg); + document.body.offsetTop; // Flush layout. + getNode("c4_child").classList.add('gentext'); + await events; + + testAccessibleTree("c4", { SECTION: [ // container + { SECTION: [ // inserted node + { STATICTEXT: [] }, // :before + { TEXT_LEAF: [] }, // primary text + { STATICTEXT: [] }, // :after + ] }, + ] }); + } + + // Test to see that generated content gets removed + async function removeCSSGeneratedContent() { + let msg = "removeCSSGeneratedContent"; + let c5_child = getAccessible("c5_child"); + info(msg); + let events = waitForEvents([ + [EVENT_HIDE, c5_child.firstChild], + [EVENT_HIDE, c5_child.lastChild], + [EVENT_REORDER, c5_child]], msg); + document.body.offsetTop; // Flush layout. + getNode("c5_child").classList.remove('gentext'); + await events; + + testAccessibleTree("c5",{ SECTION: [ // container + { SECTION: [ // inserted node + { TEXT_LEAF: [] }, // primary text + ] }, + ] }); + } + + // Test to see that a non-accessible intermediate container gets its accessible + // descendants removed and inserted correctly. + async function intermediateNonAccessibleContainers() { + let msg = "intermediateNonAccessibleContainers"; + info(msg); + + testAccessibleTree("c6",{ SECTION: [ + { SECTION: [ + { role: ROLE_PUSHBUTTON, name: "Hello" }, + ] }, + ] }); + + let events = waitForOrderedEvents( + [[EVENT_HIDE, "b1"], [EVENT_SHOW, "b2"], [EVENT_REORDER, "scrollarea"]], msg); + document.body.offsetTop; // Flush layout. + getNode("scrollarea").style.overflow = "auto"; + document.querySelector("#scrollarea > div > div:first-child").style.display = "none"; + document.querySelector("#scrollarea > div > div:last-child").style.display = "block"; + await events; + + testAccessibleTree("c6",{ SECTION: [ + { SECTION: [ + { role: ROLE_PUSHBUTTON, name: "Goodbye" }, + ] }, + ] }); + } + + // Test to see that the button gets reparented into the new accessible container. + async function intermediateNonAccessibleContainerBecomesAccessible() { + let msg = "intermediateNonAccessibleContainerBecomesAccessible"; + info(msg); + + testAccessibleTree("c7",{ SECTION: [ + { role: ROLE_PUSHBUTTON, name: "Hello" }, + { TEXT_LEAF: [] } + ] }); + + let events = waitForOrderedEvents( + [[EVENT_HIDE, "b3"], + // b3 show event coalesced into its new container + [EVENT_SHOW, evt => evt.DOMNode.classList.contains('intermediate')], + [EVENT_REORDER, "c7"]], msg); + document.body.offsetTop; // Flush layout. + document.querySelector("#c7 > div").style.display = "block"; + await events; + + testAccessibleTree("c7",{ SECTION: [ + { SECTION: [ { role: ROLE_PUSHBUTTON, name: "Hello" } ] } + ] }); + } + + // Test to ensure that relocated accessibles are removed when a DOM + // ancestor is hidden. + async function removeRelocatedWhenDomAncestorHidden() { + info("removeRelocatedWhenDomAncestorHidden"); + + testAccessibleTree("c8",{ SECTION: [ + { EDITCOMBOBOX: [ // c8_owner + { COMBOBOX_LIST: [] }, // c8_owned + ]}, + { SECTION: [] }, // c8_owned_container + ] }); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, "c8_owned_container"], + [EVENT_HIDE, "c8_owned"], + [EVENT_REORDER, "c8"], + ], "removeRelocatedWhenDomAncestorHidden"); + document.body.offsetTop; // Flush layout. + getNode("c8_owned_container").hidden = true; + await events; + + testAccessibleTree("c8",{ SECTION: [ + { EDITCOMBOBOX: [] }, // c8_owner + ] }); + } + + // Bug 1572829 + async function removeShadowRootHost() { + info("removeShadowRootHost"); + document.body.offsetTop; // Flush layout. + + let event = waitForEvent(EVENT_REORDER, "c9", "removeShadowRootHost"); + getNode("c9").firstElementChild.attachShadow({mode: "open"}); + getNode("c9").firstElementChild.replaceWith(""); + + await event; + } + + function listItemReframe() { + testAccessibleTree("li",{ LISTITEM: [ + { LISTITEM_MARKER: [] }, + { TEXT_LEAF: [] }, + ] }); + + getNode("li").style.listStylePosition = "inside"; + document.body.offsetTop; // Flush layout. + window.windowUtils.advanceTimeAndRefresh(100); + + testAccessibleTree("li",{ LISTITEM: [ + { LISTITEM_MARKER: [] }, + { TEXT_LEAF: [] }, + ] }); + + window.windowUtils.restoreNormalRefresh(); + } + + // Check to see that a reframed body gets its children pruned correctly. + async function bodyReframe(argument) { + // Load sub-document in iframe. + let event = waitForEvent(EVENT_REORDER, "iframe", "bodyReframe"); + getNode("iframe").src = + `data:text/html,<div>Hello</div><div style="display: none">World</div>`; + await event; + + // Initial tree should have one section leaf. + testAccessibleTree("c10",{ SECTION: [ + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { SECTION: [ + { role: ROLE_TEXT_LEAF, name: "Hello" } + ] } + ]} + ] } + ] }); + + + let iframeDoc = getNode("iframe").contentWindow.document; + + // Trigger coalesced reframing. Both the body node and its children + // will need reframing. + event = waitForEvent(EVENT_REORDER, iframeDoc, "bodyReframe"); + iframeDoc.body.style.display = "inline-block"; + iframeDoc.querySelector("div:first-child").style.display = "none"; + iframeDoc.querySelector("div:last-child").style.display = "block"; + + await event; + + // Only the second section should be showing + testAccessibleTree("c10",{ SECTION: [ + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { SECTION: [ + { role: ROLE_TEXT_LEAF, name: "World" } + ] } + ]} + ] } + ] }); + } + + // Ensure that embed elements recreate their Accessible if they started + // without an src and then an src is set later. + async function embedBecomesOuterDoc() { + let msg = "embedBecomesOuterDoc"; + info(msg); + + testAccessibleTree("c12", { SECTION: [ + { TEXT: [] } + ] }); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, "embed"], + [EVENT_SHOW, "embed"], + [EVENT_REORDER, "c12"], + ], msg); + getNode("embed").src = "data:text/html,"; + await events; + + testAccessibleTree("c12", { SECTION: [ + { INTERNAL_FRAME: [ + { DOCUMENT: [] } + ] } + ] }); + } + + // Test that we get a text removed event when removing generated content from a button + async function testCSSGeneratedContentRemovedFromButton() { + let msg = "testCSSGeneratedContentRemovedFromButton"; + info(msg); + + testAccessibleTree("c13", { SECTION: [ + { role: ROLE_PUSHBUTTON, name: "beforego", + children: [{ STATICTEXT: [] }, { TEXT_LEAF: [] }] } + ] }); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, evt => evt.accessible.name == "before"], + [EVENT_TEXT_REMOVED, evt => evt.accessible.role == ROLE_PUSHBUTTON], + [EVENT_SHOW, evt => evt.DOMNode.tagName == "HR"], + [EVENT_REORDER, "c13"], + ], msg); + getNode("b13").click(); + await events; + + testAccessibleTree("c13", { SECTION: [ + { role: ROLE_PUSHBUTTON, name: "go", + children: [{ TEXT_LEAF: [] }] }, + { SEPARATOR: [] } + ] }); + } + + // Slack seems to often restyle containers and change children + // simultaneously, this results in an insertion queue filled with + // redundant insertions and unparented nodes. + // This test duplicates some of this. + async function testSlack() { + let msg = "testSlack"; + info(msg); + + window.windowUtils.advanceTimeAndRefresh(100); + let event = waitForEvent(EVENT_REORDER, "c14", "testSlack"); + + let keyContainer = document.querySelector("#c14 .intermediate"); + keyContainer.style.display = "inline-block"; + document.body.offsetTop; // Flush layout. + + let one = document.querySelector("#c14 [aria-label='one']"); + let three = document.querySelector("#c14 [aria-label='three']"); + one.remove(); + three.remove(); + // insert one first + keyContainer.firstChild.before(one.cloneNode()); + // insert three last + keyContainer.lastChild.after(three.cloneNode()); + + keyContainer.style.display = "flex"; + document.body.offsetTop; // Flush layout. + + window.windowUtils.restoreNormalRefresh(); + + await event; + + is(getAccessible("c14").name, "one two three", "subtree has correct order"); + } + + // Ensure that a node is removed when visibility: hidden is set but the + // layout frame is reconstructed; e.g. because of position: fixed. Also + // ensure that visible children aren't clobbered. + async function visibilityHiddenWithReframe() { + let msg = "visibilityHiddenWithReframe"; + info(msg); + + testAccessibleTree("c15", { SECTION: [ // c15 + { SECTION: [ // c15_inner + { TEXT_LEAF: [] }, // Text + { PARAGRAPH: [ + { TEXT_LEAF: [] } // Para + ] }, + { HEADING: [ // c15_visible + { TEXT_LEAF: [] } // Visible + ] }, // c15_visible + ] } // c15_inner + ] }); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, "c15_inner"], + [EVENT_SHOW, "c15_visible"], + [EVENT_REORDER, "c15"], + ], msg); + getNode("c15_inner").style = "visibility: hidden; position: fixed;"; + await events; + + testAccessibleTree("c15", { SECTION: [ // c15 + { HEADING: [ // c15_visible + { TEXT_LEAF: [] } // Visible + ] }, // c15_visible + ] }); + } + + async function doTest() { + await hideDivFromInsideSpan(); + + await showDivFromInsideSpan(); + + await removeDivFromInsideSpan(); + + await addCSSGeneratedContent(); + + await removeCSSGeneratedContent(); + + await intermediateNonAccessibleContainers(); + + await intermediateNonAccessibleContainerBecomesAccessible(); + + await removeRelocatedWhenDomAncestorHidden(); + + await removeShadowRootHost(); + + listItemReframe(); + + await bodyReframe(); + + await embedBecomesOuterDoc(); + + await testCSSGeneratedContentRemovedFromButton(); + + await testSlack(); + + await visibilityHiddenWithReframe(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="c1"> + <span role="region" id="span1" aria-label="region"><div id="div1">hello</div></span> + </div> + + <div id="c2"> + <span role="region" id="span2" aria-label="region"><div id="div2" style="display: none">hello</div></span> + </div> + + <div id="c3"> + <span role="region" id="span3" aria-label="region"><div id="div3">hello</div></span> + </div> + + <div id="c4"><div id="c4_child">text</div></div> + + <div id="c5"><div id="c5_child" class="gentext">text</div></div> + + <div id="c6"> + <div id="scrollarea" style="overflow:hidden;"> + <div><div role="none"><button id="b1">Hello</button></div><div role="none" style="display: none"><button id="b2">Goodbye</button></div></div> + </div> + </div> + + <div id="c7"> + <div style="display: inline;" class="intermediate"> + <button id="b3">Hello</button> + </div> + </div> + + <div id="c8"> + <div id="c8_owner" role="combobox" aria-owns="c8_owned"></div> + <div id="c8_owned_container"> + <div id="c8_owned" role="listbox"></div> + </div> + </div> + + <div id="c9"> + <div><dir>a</dir></div> + </div> + + <div id="c11"> + <ul> + <li id="li">Test</li> + </ul> + </div> + + <div id="c12"><embed id="embed"></embed></div> + + <div id="c10"> + <iframe id="iframe"></iframe> + </div> + + <div id="c13"> + <style> + .before::before { content: 'before' } + </style> + <button id="b13" class="before" onclick="this.className = ''; this.insertAdjacentElement('afterend', document.createElement('hr'))">go</button> + </div> + + <div role="heading" id="c14" data-qa="virtual-list-item"> + <div class="intermediate"> + <div role="img" aria-label="one"></div> two <div role="img" + aria-label="three"></div> + </div> + </div> + + <div id="c15"><div id="c15_inner"> + Text + <p>Para</p> + <h1 id="c15_visible" style="visibility: visible;">Visible</h1> + </div></div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_doc.html b/accessible/tests/mochitest/treeupdate/test_doc.html new file mode 100644 index 0000000000..6bb2863df4 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_doc.html @@ -0,0 +1,415 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test document root content mutations</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="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Helpers + + function getDocNode(aID) { + return getNode(aID).contentDocument; + } + function getDocChildNode(aID) { + return getDocNode(aID).body.firstChild; + } + + function rootContentReplaced(aID, aTextName, aRootContentRole) { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getDocChildNode, aID), + new invokerChecker(EVENT_REORDER, getDocNode, aID), + ]; + + this.finalCheck = function rootContentReplaced_finalCheck() { + var tree = { + role: aRootContentRole || ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: aTextName, + }, + ], + }; + testAccessibleTree(getDocNode(aID), tree); + }; + } + + function rootContentRemoved(aID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, null), + new invokerChecker(EVENT_REORDER, getDocNode, aID), + ]; + + this.preinvoke = function rootContentRemoved_preinvoke() { + // Set up target for hide event before we invoke. + var text = getAccessible(getAccessible(getDocNode(aID)).firstChild); + this.eventSeq[0].target = text; + }; + + this.finalCheck = function rootContentRemoved_finalCheck() { + var tree = { + role: ROLE_DOCUMENT, + children: [ ], + }; + testAccessibleTree(getDocNode(aID), tree); + }; + } + + function rootContentInserted(aID, aTextName) { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getDocChildNode, aID), + new invokerChecker(EVENT_REORDER, getDocNode, aID), + ]; + + this.finalCheck = function rootContentInserted_finalCheck() { + var tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: aTextName, + }, + ], + }; + testAccessibleTree(getDocNode(aID), tree); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function writeIFrameDoc(aID) { + this.__proto__ = new rootContentReplaced(aID, "hello"); + + this.invoke = function writeIFrameDoc_invoke() { + var docNode = getDocNode(aID); + + // We can't use open/write/close outside of iframe document because of + // security error. + var script = docNode.createElement("script"); + script.textContent = "document.open(); document.write('hello'); document.close();"; + docNode.body.appendChild(script); + }; + + this.getID = function writeIFrameDoc_getID() { + return "write document"; + }; + } + + /** + * Replace HTML element. + */ + function replaceIFrameHTMLElm(aID) { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getDocChildNode, aID), + new invokerChecker(EVENT_REORDER, getDocNode, aID), + ]; + + this.invoke = function replaceIFrameHTMLElm_invoke() { + var docNode = getDocNode(aID); + var newHTMLNode = docNode.createElement("html"); + newHTMLNode.innerHTML = `<body><p>New Wave</p></body`; + docNode.replaceChild(newHTMLNode, docNode.documentElement); + }; + + this.finalCheck = function replaceIFrameHTMLElm_finalCheck() { + var tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_PARAGRAPH, + children: [ + { + role: ROLE_TEXT_LEAF, + name: "New Wave", + }, + ], + }, + ], + }; + testAccessibleTree(getDocNode(aID), tree); + }; + + this.getID = function replaceIFrameHTMLElm_getID() { + return "replace HTML element"; + }; + } + + /** + * Replace HTML body on new body having ARIA role. + */ + function replaceIFrameBody(aID) { + this.__proto__ = new rootContentReplaced(aID, "New Hello"); + + this.invoke = function replaceIFrameBody_invoke() { + var docNode = getDocNode(aID); + var newBodyNode = docNode.createElement("body"); + var newTextNode = docNode.createTextNode("New Hello"); + newBodyNode.appendChild(newTextNode); + docNode.documentElement.replaceChild(newBodyNode, docNode.body); + }; + + this.getID = function replaceIFrameBody_getID() { + return "replace body"; + }; + } + + /** + * Replace HTML body on new body having ARIA role. + */ + function replaceIFrameBodyOnARIARoleBody(aID) { + this.__proto__ = new rootContentReplaced(aID, "New Hello", + ROLE_APPLICATION); + + this.invoke = function replaceIFrameBodyOnARIARoleBody_invoke() { + var docNode = getDocNode(aID); + var newBodyNode = docNode.createElement("body"); + var newTextNode = docNode.createTextNode("New Hello"); + newBodyNode.appendChild(newTextNode); + newBodyNode.setAttribute("role", "application"); + docNode.documentElement.replaceChild(newBodyNode, docNode.body); + }; + + this.getID = function replaceIFrameBodyOnARIARoleBody_getID() { + return "replace body on body having ARIA role"; + }; + } + + /** + * Open/close document pair. + */ + function openIFrameDoc(aID) { + this.__proto__ = new rootContentRemoved(aID); + + this.invoke = function openIFrameDoc_invoke() { + this.preinvoke(); + + // Open document. + var docNode = getDocNode(aID); + var script = docNode.createElement("script"); + script.textContent = "function closeMe() { document.write('Works?'); document.close(); } window.closeMe = closeMe; document.open();"; + docNode.body.appendChild(script); + }; + + this.getID = function openIFrameDoc_getID() { + return "open document"; + }; + } + + function closeIFrameDoc(aID) { + this.__proto__ = new rootContentInserted(aID, "Works?"); + + this.invoke = function closeIFrameDoc_invoke() { + // Write and close document. + getDocNode(aID).write("Works?"); getDocNode(aID).close(); + }; + + this.getID = function closeIFrameDoc_getID() { + return "close document"; + }; + } + + /** + * Remove/insert HTML element pair. + */ + function removeHTMLFromIFrameDoc(aID) { + this.__proto__ = new rootContentRemoved(aID); + + this.invoke = function removeHTMLFromIFrameDoc_invoke() { + this.preinvoke(); + + // Remove HTML element. + var docNode = getDocNode(aID); + docNode.firstChild.remove(); + }; + + this.getID = function removeHTMLFromIFrameDoc_getID() { + return "remove HTML element"; + }; + } + + function insertHTMLToIFrameDoc(aID) { + this.__proto__ = new rootContentInserted(aID, "Haha"); + + this.invoke = function insertHTMLToIFrameDoc_invoke() { + // Insert HTML element. + var docNode = getDocNode(aID); + var html = docNode.createElement("html"); + var body = docNode.createElement("body"); + var text = docNode.createTextNode("Haha"); + body.appendChild(text); + html.appendChild(body); + docNode.appendChild(html); + }; + + this.getID = function insertHTMLToIFrameDoc_getID() { + return "insert HTML element document"; + }; + } + + /** + * Remove/insert HTML body pair. + */ + function removeBodyFromIFrameDoc(aID) { + this.__proto__ = new rootContentRemoved(aID); + + this.invoke = function removeBodyFromIFrameDoc_invoke() { + this.preinvoke(); + + // Remove body element. + var docNode = getDocNode(aID); + docNode.documentElement.removeChild(docNode.body); + }; + + this.getID = function removeBodyFromIFrameDoc_getID() { + return "remove body element"; + }; + } + + function insertElmUnderDocElmWhileBodyMissed(aID) { + this.docNode = null; + this.inputNode = null; + + function getInputNode() { return this.inputNode; } + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getInputNode.bind(this)), + new invokerChecker(EVENT_REORDER, getDocNode, aID), + ]; + + this.invoke = function invoke() { + this.docNode = getDocNode(aID); + this.inputNode = this.docNode.createElement("input"); + this.docNode.documentElement.appendChild(this.inputNode); + }; + + this.finalCheck = function finalCheck() { + var tree = + { DOCUMENT: [ + { ENTRY: [ ] }, + ] }; + testAccessibleTree(this.docNode, tree); + + // Remove aftermath of this test before next test starts. + this.docNode.documentElement.removeChild(this.inputNode); + }; + + this.getID = function getID() { + return "Insert element under document element while body is missed."; + }; + } + + function insertBodyToIFrameDoc(aID) { + this.__proto__ = new rootContentInserted(aID, "Yo ho ho i butylka roma!"); + + this.invoke = function insertBodyToIFrameDoc_invoke() { + // Insert body element. + var docNode = getDocNode(aID); + var body = docNode.createElement("body"); + var text = docNode.createTextNode("Yo ho ho i butylka roma!"); + body.appendChild(text); + docNode.documentElement.appendChild(body); + }; + + this.getID = function insertBodyToIFrameDoc_getID() { + return "insert body element"; + }; + } + + function changeSrc(aID) { + this.containerNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function changeSrc_invoke() { + this.containerNode.src = "data:text/html,<html><input></html>"; + }; + + this.finalCheck = function changeSrc_finalCheck() { + var tree = + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { ENTRY: [ ] }, + ] }, + ] }; + testAccessibleTree(this.containerNode, tree); + }; + + this.getID = function changeSrc_getID() { + return "change src on iframe"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpToConsole = true; + // enableLogging('tree,verbose'); + + var gQueue = null; + + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new writeIFrameDoc("iframe")); + gQueue.push(new replaceIFrameHTMLElm("iframe")); + gQueue.push(new replaceIFrameBody("iframe")); + gQueue.push(new openIFrameDoc("iframe")); + gQueue.push(new closeIFrameDoc("iframe")); + gQueue.push(new removeHTMLFromIFrameDoc("iframe")); + gQueue.push(new insertHTMLToIFrameDoc("iframe")); + gQueue.push(new removeBodyFromIFrameDoc("iframe")); + gQueue.push(new insertElmUnderDocElmWhileBodyMissed("iframe")); + gQueue.push(new insertBodyToIFrameDoc("iframe")); + gQueue.push(new changeSrc("iframe")); + gQueue.push(new replaceIFrameBodyOnARIARoleBody("iframe")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Update accessible tree when root element is changed" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=606082">Mozilla Bug 606082</a> + <a target="_blank" + title="Elements inserted outside the body aren't accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a> + <a target="_blank" + title="Reorder event for document must be fired after document initial tree creation" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=669263">Mozilla Bug 669263</a> + <a target="_blank" + title="Changing the HTML body doesn't pick up ARIA role" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=818407">Mozilla Bug 818407</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <iframe id="iframe"></iframe> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_gencontent.html b/accessible/tests/mochitest/treeupdate/test_gencontent.html new file mode 100644 index 0000000000..9a0c107133 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_gencontent.html @@ -0,0 +1,187 @@ +<html> + +<head> + <title>Elements with CSS generated content</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + .gentext:before { + content: "START" + } + .gentext:after { + content: "END" + } + </style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + // ////////////////////////////////////////////////////////////////////////// + + /** + * Insert new node with CSS generated content style applied to container. + */ + function insertNodeHavingGenContent(aContainerID) { + this.containerNode = getNode(aContainerID); + this.container = getAccessible(this.containerNode); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getFirstChild, this.container), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function insertNodeHavingGenContent_invoke() { + var node = document.createElement("div"); + node.textContent = "text"; + node.setAttribute("class", "gentext"); + this.containerNode.appendChild(node); + }; + + this.finalCheck = function insertNodeHavingGenContent_finalCheck() { + var accTree = + { SECTION: [ // container + { SECTION: [ // inserted node + { STATICTEXT: [] }, // :before + { TEXT_LEAF: [] }, // primary text + { STATICTEXT: [] }, // :after + ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function insertNodeHavingGenContent_getID() { + return "insert node having generated content to " + prettyName(aContainerID); + }; + } + + /** + * Add CSS generated content to the given node contained by container node. + */ + function addGenContent(aContainerID, aNodeID) { + this.container = getAccessible(aContainerID); + this.nodeAcc = getAccessible(aNodeID); + this.node = getNode(aNodeID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getFirstChild, this.nodeAcc), + new invokerChecker(EVENT_SHOW, getLastChild, this.nodeAcc), + new invokerChecker(EVENT_REORDER, this.nodeAcc), + ]; + + this.invoke = function addGenContent_invoke() { + this.node.classList.add("gentext"); + }; + + this.finalCheck = function insertNodeHavingGenContent_finalCheck() { + var accTree = + { SECTION: [ // container + { SECTION: [ // inserted node + { STATICTEXT: [] }, // :before + { TEXT_LEAF: [] }, // primary text + { STATICTEXT: [] }, // :after + ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function addGenContent_getID() { + return "add generated content to" + prettyName(aNodeID); + }; + } + + /** + * Remove CSS generated content from the given node contained by container node. + */ + function removeGenContent(aContainerID, aNodeID) { + this.container = getAccessible(aContainerID); + this.nodeAcc = getAccessible(aNodeID); + this.node = getNode(aNodeID); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.nodeAcc.lastChild), + new invokerChecker(EVENT_HIDE, this.nodeAcc.firstChild), + new invokerChecker(EVENT_REORDER, this.nodeAcc), + ]; + + this.invoke = function removeGenContent_invoke() { + this.node.classList.remove("gentext"); + }; + + this.finalCheck = function removeGenContent_finalCheck() { + var accTree = + { SECTION: [ // container + { SECTION: [ // inserted node + { TEXT_LEAF: [] }, // primary text + ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function addGenContent_getID() { + return "remove generated content from" + prettyName(aNodeID); + }; + } + /** + * Target getters. + */ + function getFirstChild(aAcc) { + try { return aAcc.firstChild; } catch (e) { return null; } + } + + function getLastChild(aAcc) { + try { return aAcc.lastChild; } catch (e) { return null; } + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + // ////////////////////////////////////////////////////////////////////////// + + var gQueue = null; + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new insertNodeHavingGenContent("container1")); + gQueue.push(new addGenContent("container2", "container2_child")); + gQueue.push(new removeGenContent("container3", "container3_child")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=646350" + title="Add a test for dynamic chnages of CSS generated content"> + Mozilla Bug 646350</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div id="container1"></div> + <div id="container2"><div id="container2_child">text</div></div> + <div id="container3"><div id="container3_child" class="gentext">text</div></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_general.html b/accessible/tests/mochitest/treeupdate/test_general.html new file mode 100644 index 0000000000..8129cae98a --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_general.html @@ -0,0 +1,174 @@ +<html> + +<head> + <title>Testing the tree updates</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + // ////////////////////////////////////////////////////////////////////////// + + function prependAppend(aContainer) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aContainer), + ]; + + this.invoke = function prependAppend_invoke() { + var checkbox = document.createElement("input"); + checkbox.setAttribute("type", "checkbox"); + getNode(aContainer).insertBefore(checkbox, getNode(aContainer).firstChild); + + var button = document.createElement("input"); + button.setAttribute("type", "button"); + getNode(aContainer).appendChild(button); + }; + + this.finalCheck = function prependAppend_finalCheck() { + var accTree = + { SECTION: [ // container + { CHECKBUTTON: [ ] }, + { ENTRY: [ ] }, + { PUSHBUTTON: [ ] }, + ] }; + testAccessibleTree(aContainer, accTree); + }; + + this.getID = function prependAppend_getID() { + return "prepends a child and appends a child"; + }; + } + + function removeRemove(aContainer) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aContainer), + ]; + + this.invoke = function removeRemove_invoke() { + getNode(aContainer).firstChild.remove(); + }; + + this.finalCheck = function removeRemove_finalCheck() { + var accTree = + { SECTION: [ // container + { PUSHBUTTON: [ ] }, + ] }; + testAccessibleTree(aContainer, accTree); + }; + + this.getID = function removeRemove_getID() { + return "remove first and second children"; + }; + } + + function insertInaccessibleAccessibleSiblings() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "c3"), + ]; + + this.invoke = function insertInaccessibleAccessibleSiblings_invoke() { + getNode("c3").appendChild(document.createElement("span")); + getNode("c3").appendChild(document.createElement("input")); + }; + + this.finalCheck = function insertInaccessibleAccessibleSiblings_finalCheck() { + var accTree = + { SECTION: [ // container + { PUSHBUTTON: [ + { TEXT_LEAF: [] }, + ] }, + { ENTRY: [ ] }, + ] }; + testAccessibleTree("c3", accTree); + }; + + this.getID = function insertInaccessibleAccessibleSiblings_getID() { + return "insert inaccessible and then accessible siblings"; + }; + } + + // Test for bug 1500416. + function displayContentsInsertion() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "c4"), + ]; + + this.invoke = function displayContentsInsertion_invoke() { + document.body.offsetTop; // Flush layout. + + let list = document.createElement("ul"); + list.style.display = "contents"; + list.appendChild(document.createElement("li")); + list.firstChild.appendChild(document.createTextNode("Text")); + getNode("c4").appendChild(list); + }; + + this.finalCheck = function displayContentsInsertion_finalCheck() { + var accTree = + { SECTION: [ // container + { LIST: [ + { LISTITEM: [ + { LISTITEM_MARKER: [] }, + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }; + testAccessibleTree("c4", accTree); + }; + + this.getID = function displayContentsInsertion_getID() { + return "insert accessible display: contents element."; + }; + } + + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + // ////////////////////////////////////////////////////////////////////////// + + var gQueue = null; + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new prependAppend("c1")); + gQueue.push(new removeRemove("c2")); + gQueue.push(new insertInaccessibleAccessibleSiblings()); + gQueue.push(new displayContentsInsertion()); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="c1"><input></div> + <div id="c2"><span><input type="checkbox"><input></span><input type="button"></div> + + <div id="c3"><input type="button" value="button"></div> + <div id="c4"></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_hidden.html b/accessible/tests/mochitest/treeupdate/test_hidden.html new file mode 100644 index 0000000000..e687fc97c9 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_hidden.html @@ -0,0 +1,125 @@ +<!DOCTYPE html> +<html> + +<head> + <title>@hidden attribute 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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + // ////////////////////////////////////////////////////////////////////////// + + /** + * Set @hidden attribute + */ + function setHiddenAttr(aContainerID, aChildID) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function setHiddenAttr_invoke() { + var tree = + { SECTION: [ + { ENTRY: [ + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aChildID).setAttribute("hidden", "true"); + }; + + this.finalCheck = function setHiddenAttr_finalCheck() { + var tree = + { SECTION: [ + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function setHiddenAttr_getID() { + return "Set @hidden attribute on input and test accessible tree for div"; + }; + } + + /** + * Remove @hidden attribute + */ + function removeHiddenAttr(aContainerID, aChildID) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function removeHiddenAttr_invoke() { + var tree = + { SECTION: [ + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aChildID).removeAttribute("hidden"); + }; + + this.finalCheck = function removeHiddenAttr_finalCheck() { + var tree = + { SECTION: [ + { ENTRY: [ + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function removeHiddenAttr_getID() { + return "Remove @hidden attribute on input and test accessible tree for div"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + // ////////////////////////////////////////////////////////////////////////// + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new setHiddenAttr("container", "child")); + gQueue.push(new removeHiddenAttr("container", "child")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + + </script> + +</head> + +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="container"><input id="child"></div> + + <div id="eventdump"></div> + +</body> + +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_imagemap.html b/accessible/tests/mochitest/treeupdate/test_imagemap.html new file mode 100644 index 0000000000..7befef6905 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_imagemap.html @@ -0,0 +1,402 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML img map accessible tree update tests</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 src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function insertArea(aImageMapID, aMapID) { + this.imageMap = getAccessible(aImageMapID); + this.mapNode = getNode(aMapID); + + function getInsertedArea(aThisObj) { + return aThisObj.imageMap.firstChild; + } + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getInsertedArea, this), + new invokerChecker(EVENT_REORDER, this.imageMap), + ]; + + this.invoke = function insertArea_invoke() { + var areaElm = document.createElement("area"); + areaElm.setAttribute("href", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.bbc.co.uk/radio4/atoz/index.shtml#a"); + areaElm.setAttribute("coords", "0,0,13,14"); + areaElm.setAttribute("alt", "a"); + areaElm.setAttribute("shape", "rect"); + + this.mapNode.insertBefore(areaElm, this.mapNode.firstChild); + }; + + this.finalCheck = function insertArea_finalCheck() { + var accTree = + { IMAGE_MAP: [ + { + role: ROLE_LINK, + name: "a", + children: [ ], + }, + { + role: ROLE_LINK, + name: "b", + children: [ ], + }, + ] }; + testAccessibleTree(this.imageMap, accTree); + }; + + this.getID = function insertArea_getID() { + return "insert area element"; + }; + } + + function appendArea(aImageMapID, aMapID) { + this.imageMap = getAccessible(aImageMapID); + this.mapNode = getNode(aMapID); + + function getAppendedArea(aThisObj) { + return aThisObj.imageMap.lastChild; + } + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getAppendedArea, this), + new invokerChecker(EVENT_REORDER, this.imageMap), + ]; + + this.invoke = function appendArea_invoke() { + var areaElm = document.createElement("area"); + areaElm.setAttribute("href", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.bbc.co.uk/radio4/atoz/index.shtml#c"); + areaElm.setAttribute("coords", "34,0,47,14"); + areaElm.setAttribute("alt", "c"); + areaElm.setAttribute("shape", "rect"); + + this.mapNode.appendChild(areaElm); + }; + + this.finalCheck = function appendArea_finalCheck() { + var accTree = + { IMAGE_MAP: [ + { + role: ROLE_LINK, + name: "a", + children: [ ], + }, + { + role: ROLE_LINK, + name: "b", + children: [ ], + }, + { + role: ROLE_LINK, + name: "c", + children: [ ], + }, + ] }; + testAccessibleTree(this.imageMap, accTree); + }; + + this.getID = function appendArea_getID() { + return "append area element"; + }; + } + + function removeArea(aImageMapID, aMapID) { + this.imageMap = getAccessible(aImageMapID); + this.area = null; + this.mapNode = getNode(aMapID); + + function getRemovedArea(aThisObj) { + return aThisObj.area; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getRemovedArea, this), + new invokerChecker(EVENT_REORDER, this.imageMap), + ]; + + this.invoke = function removeArea_invoke() { + this.area = this.imageMap.firstChild; + this.mapNode.removeChild(this.mapNode.firstElementChild); + }; + + this.finalCheck = function removeArea_finalCheck() { + var accTree = + { IMAGE_MAP: [ + { + role: ROLE_LINK, + name: "b", + children: [ ], + }, + { + role: ROLE_LINK, + name: "c", + children: [ ], + }, + ] }; + testAccessibleTree(this.imageMap, accTree); + }; + + this.getID = function removeArea_getID() { + return "remove area element"; + }; + } + + function removeNameOnMap(aImageMapContainerID, aImageMapID, aMapID) { + this.container = getAccessible(aImageMapContainerID); + this.containerNode = this.container.DOMNode; + this.imageMap = getAccessible(aImageMapID); + this.imgNode = this.imageMap.DOMNode; + this.mapNode = getNode(aMapID); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.imageMap), + new invokerChecker(EVENT_SHOW, this.imgNode), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function removeNameOnMap_invoke() { + this.mapNode.removeAttribute("name"); + }; + + this.finalCheck = function removeNameOnMap_finalCheck() { + var accTree = + { SECTION: [ + { GRAPHIC: [ ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function removeNameOnMap_getID() { + return "remove @name on map element"; + }; + } + + function restoreNameOnMap(aImageMapContainerID, aImageMapID, aMapID) { + this.container = getAccessible(aImageMapContainerID); + this.containerNode = this.container.DOMNode; + this.imageMap = null; + this.imgNode = getNode(aImageMapID); + this.mapNode = getNode(aMapID); + + function getImageMap(aThisObj) { + return aThisObj.imageMap; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getImageMap, this), + new invokerChecker(EVENT_SHOW, this.imgNode), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function restoreNameOnMap_invoke() { + this.imageMap = getAccessible(aImageMapID); + this.mapNode.setAttribute("name", "atoz_map"); + + // XXXhack: force repainting of the image (see bug 745788 for details). + waveOverImageMap(aImageMapID); + }; + + this.finalCheck = function removeNameOnMap_finalCheck() { + var accTree = + { SECTION: [ + { IMAGE_MAP: [ + { LINK: [ ] }, + { LINK: [ ] }, + ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function removeNameOnMap_getID() { + return "restore @name on map element"; + }; + } + + function removeMap(aImageMapContainerID, aImageMapID, aMapID) { + this.container = getAccessible(aImageMapContainerID); + this.containerNode = this.container.DOMNode; + this.imageMap = null; + this.imgNode = getNode(aImageMapID); + this.mapNode = getNode(aMapID); + + function getImageMap(aThisObj) { + return aThisObj.imageMap; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getImageMap, this), + new invokerChecker(EVENT_SHOW, this.imgNode), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function removeMap_invoke() { + this.imageMap = getAccessible(aImageMapID); + this.mapNode.remove(); + }; + + this.finalCheck = function removeMap_finalCheck() { + var accTree = + { SECTION: [ + { GRAPHIC: [ ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function removeMap_getID() { + return "remove map element"; + }; + } + + function insertMap(aImageMapContainerID, aImageID) { + this.container = getAccessible(aImageMapContainerID); + this.containerNode = this.container.DOMNode; + this.image = null; + this.imgMapNode = getNode(aImageID); + + function getImage(aThisObj) { + return aThisObj.image; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getImage, this), + new invokerChecker(EVENT_SHOW, this.imgMapNode), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function insertMap_invoke() { + this.image = getAccessible(aImageID); + + var map = document.createElement("map"); + map.setAttribute("name", "atoz_map"); + map.setAttribute("id", "map"); + + var area = document.createElement("area"); + area.setAttribute("href", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.bbc.co.uk/radio4/atoz/index.shtml#b"); + area.setAttribute("coords", "17,0,30,14"); + area.setAttribute("alt", "b"); + area.setAttribute("shape", "rect"); + + map.appendChild(area); + + this.containerNode.appendChild(map); + }; + + this.finalCheck = function insertMap_finalCheck() { + var accTree = + { SECTION: [ + { IMAGE_MAP: [ + { LINK: [ ] }, + ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function insertMap_getID() { + return "insert map element"; + }; + } + + function hideImageMap(aContainerID, aImageID) { + this.container = getAccessible(aContainerID); + this.imageMap = null; + this.imageMapNode = getNode(aImageID); + + function getImageMap(aThisObj) { + return aThisObj.imageMap; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getImageMap, this), + new invokerChecker(EVENT_REORDER, aContainerID), + ]; + + this.invoke = function hideImageMap_invoke() { + this.imageMap = getAccessible(this.imageMapNode); + this.imageMapNode.style.display = "none"; + }; + + this.finalCheck = function hideImageMap_finalCheck() { + var accTree = + { SECTION: [ ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function hideImageMap_getID() { + return "display:none image"; + }; + } + + // gA11yEventDumpToConsole = true; // debug stuff + function doPreTest() { + waitForImageMap("imgmap", doTest); + } + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new insertArea("imgmap", "map")); + gQueue.push(new appendArea("imgmap", "map")); + gQueue.push(new removeArea("imgmap", "map")); + gQueue.push(new removeNameOnMap("container", "imgmap", "map")); + gQueue.push(new restoreNameOnMap("container", "imgmap", "map")); + gQueue.push(new removeMap("container", "imgmap", "map")); + gQueue.push(new insertMap("container", "imgmap")); + gQueue.push(new hideImageMap("container", "imgmap")); + + // enableLogging("tree"); // debug stuff + // gQueue.onFinish = function() { disableLogging("tree"); } + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doPreTest); + </script> + +</head> +<body> + + <a target="_blank" + title="Image map accessible tree is not updated when image map is changed" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=732389"> + Mozilla Bug 732389 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <map name="atoz_map" id="map"> + <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b" + coords="17,0,30,14" alt="b" shape="rect"> + </map> + + <div id="container"> + <img id="imgmap" width="447" height="15" + usemap="#atoz_map" + src="../letters.gif"><!-- + Important: no whitespace between the <img> and the </div>, so we + don't end up with textframes there, because those would be reflected + in our accessible tree in some cases. + --></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_inert.html b/accessible/tests/mochitest/treeupdate/test_inert.html new file mode 100644 index 0000000000..0364f6612b --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_inert.html @@ -0,0 +1,138 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Inert tree update test</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 src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script src="../common.js"></script> + <script src="../promisified-events.js"></script> + <script src="../role.js"></script> + + <script> + async function doTest() { + if (SpecialPowers.getBoolPref("html5.inert.enabled")) { + const inertContainer = getAccessible("inertContainer"); + const inertTree = { SECTION: [ // inertContainer + { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // before + { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // after + ]}; + testAccessibleTree(inertContainer, inertTree); + + info("Unsetting inert"); + let reordered = waitForEvent(EVENT_REORDER, inertContainer); + const inertNode = getNode("inert"); + inertNode.inert = false; + await reordered; + testAccessibleTree(inertContainer, { SECTION: [ // inertContainer + { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // before + { SECTION: [ // inert + { TEXT_LEAF: [] }, + { PUSHBUTTON: [] }, + ] }, + { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // after + ]}); + + info("Setting inert"); + reordered = waitForEvent(EVENT_REORDER, inertContainer); + inertNode.inert = true; + await reordered; + testAccessibleTree(inertContainer, inertTree); + } else { + // It's too late to flip the pref now. We'd need to load a new document + // to pick up the change. + todo(false, "Inert not enabled, skipping inert tests"); + } + + const noDialogTree = { SECTION: [ // dialogContainer + { TEXT_LEAF: [] } + ] }; + testAccessibleTree("dialogContainer", noDialogTree); + + info("Showing modal dialog"); + let reordered = waitForEvent(EVENT_REORDER, document); + const dialogNode = getNode("dialog"); + dialogNode.showModal(); + await reordered; + // The dialog makes everything else in the document inert. + testAccessibleTree(document, { DOCUMENT: [ + { DIALOG: [ + { PUSHBUTTON: [] } + ] } + ] }); + + info("Closing dialog"); + reordered = waitForEvent(EVENT_REORDER, document); + dialogNode.close(); + await reordered; + testAccessibleTree("dialogContainer", noDialogTree); + + const fullscreenTree = { SECTION: [ // fullscreen + { PUSHBUTTON: [] } + ] }; + const notFullscreenTree = { SECTION: [ // fullscreenContainer + { SECTION: [ + { PUSHBUTTON: [] } // requestFullscreen + ] }, + fullscreenTree, + ] }; + testAccessibleTree("fullscreenContainer", notFullscreenTree); + + info("Requesting fullscreen"); + reordered = waitForEvent(EVENT_REORDER, document); + // Fullscreen must be requested by a user input event. + synthesizeMouseAtCenter(getNode("requestFullscreen"), {}); + await reordered; + // Fullscreen makes everything else in the document inert. + testAccessibleTree(document, { DOCUMENT: [ + fullscreenTree + ] }); + + info("Exiting fullscreen"); + reordered = waitForEvent(EVENT_REORDER, document); + document.exitFullscreen(); + await reordered; + testAccessibleTree("fullscreenContainer", notFullscreenTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="inertContainer"> + <p>before</p> + <div id="inert" inert>inert <button></button></div> + <p>after</p> + </div> + + <div id="dialogContainer"> + dialogContainer + <dialog id="dialog"><button></button></dialog> + </div> + + <div id="fullscreenContainer"> + <div> + <button id="requestFullscreen" + onclick="document.getElementById('fullscreen').requestFullscreen();"> + </button> + </div> + <div id="fullscreen"><button></button></div> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_inner_reorder.html b/accessible/tests/mochitest/treeupdate/test_inner_reorder.html new file mode 100644 index 0000000000..b4411833d7 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_inner_reorder.html @@ -0,0 +1,148 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible delayed removal</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="../role.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + + async function testInnerReorder() { + window.windowUtils.advanceTimeAndRefresh(100); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, "c1.1.1"], + [EVENT_SHOW, "c1.1.1"], + [EVENT_INNER_REORDER, "c1.1"], + [EVENT_REORDER, "c1"], + ], "events yay"); + + let child = getNode("c1.1.1"); + child.remove(); + getNode("c1").appendChild(child); + + window.windowUtils.restoreNormalRefresh(); + + await events; + } + + async function testInnerReorderEntry() { + window.windowUtils.advanceTimeAndRefresh(100); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, e => e.accessible.name == "hello"], + [EVENT_HIDE, "c2.2"], + [EVENT_INNER_REORDER, "c2.1"], + [EVENT_REORDER, "c2"], + [EVENT_TEXT_VALUE_CHANGE, "c2.1"], + ], "events yay"); + + getNode("c2.1.1").remove(); + getNode("c2.2").remove(); + + window.windowUtils.restoreNormalRefresh(); + + await events; + } + + async function testInnerReorderAriaOwns() { + let events = waitForOrderedEvents([ + [EVENT_HIDE, "c3.1.1"], + [EVENT_SHOW, "c3.1.1"], + [EVENT_INNER_REORDER, "c3.1"], + [EVENT_REORDER, "c3"], + ], "events yay"); + + getNode("c3").setAttribute("aria-owns", "c3.1.1"); + + await events; + + events = waitForOrderedEvents([ + [EVENT_HIDE, "c3.1.1"], + [EVENT_SHOW, "c3.1.1"], + [EVENT_INNER_REORDER, "c3.1"], + [EVENT_REORDER, "c3"], + ], "events yay"); + + getNode("c3").removeAttribute("aria-owns"); + + await events; + } + + async function testInnerContainerRemoved() { + window.windowUtils.advanceTimeAndRefresh(100); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, "c4.1"], + [EVENT_SHOW, "c4.1.1"], + [EVENT_REORDER, "c4"], + ], "events yay"); + + let child = getNode("c4.1.1"); + child.remove(); + getNode("c1").appendChild(child); + getNode("c4.1").remove(); + + window.windowUtils.restoreNormalRefresh(); + + await events; + } + + + async function doTest() { + await testInnerReorder(); + + await testInnerReorderEntry(); + + await testInnerReorderAriaOwns(); + + await testInnerContainerRemoved(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="c1"> + <div id="c1.1"><div id="c1.1.1">hello</div></div> + </div> + + <div id="c2"> + <div role="textbox" contenteditable="true" id="c2.1"> + <span id="c2.1.1">hello</span> + </div> + <input type="submit" id="c2.2"> + </div> + + <div id="c3"> + <div id="c3.1"><div id="c3.1.1"></div></div> + </div> + + <div id="c4"> + <div id="c4.1"><div id="c4.1.1">hello</div></div> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_list.html b/accessible/tests/mochitest/treeupdate/test_list.html new file mode 100644 index 0000000000..06c308e422 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_list.html @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test HTML li and listitem bullet accessible cache</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Helpers + + function testLiAccessibleTree() { + // Test accessible tree. + var accTree = { + role: ROLE_LISTITEM, + children: [ + { + role: ROLE_LISTITEM_MARKER, + children: [], + }, + { + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }; + + testAccessibleTree("li", accTree); + } + + // ////////////////////////////////////////////////////////////////////////// + // Sequence item processors + + function hideProcessor() { + this.liNode = getNode("li"); + this.li = getAccessible(this.liNode); + this.bullet = this.li.firstChild; + + this.process = function hideProcessor_process() { + this.liNode.style.display = "none"; + }; + + this.onProcessed = function hideProcessor_onProcessed() { + window.setTimeout( + function(aLiAcc, aLiNode, aBulletAcc) { + testDefunctAccessible(aLiAcc, aLiNode); + testDefunctAccessible(aBulletAcc); + + gSequence.processNext(); + }, + 0, this.li, this.liNode, this.bullet + ); + }; + } + + function showProcessor() { + this.liNode = getNode("li"); + + this.process = function showProcessor_process() { + this.liNode.style.display = "list-item"; + }; + + this.onProcessed = function showProcessor_onProcessed() { + testLiAccessibleTree(); + gSequence.processNext(); + }; + } + + function textReplaceProcessor() { + this.liNode = getNode("li"); + + this.process = function textReplaceProcessor_process() { + this.liNode.textContent = "hey"; + }; + + this.onProcessed = function textReplaceProcessor_onProcessed() { + var tree = { + LISTITEM: [ + { LISTITEM_MARKER: [] }, + { TEXT_LEAF: [] }, + ], + }; + testAccessibleTree(this.liNode, tree); + SimpleTest.finish(); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpToConsole = true; + + var gSequence = null; + function doTest() { + testLiAccessibleTree(); + + gSequence = new sequence(); + + gSequence.append(new hideProcessor(), EVENT_HIDE, getAccessible("li"), + "hide HTML li"); + gSequence.append(new showProcessor(), EVENT_SHOW, getNode("li"), + "show HTML li"); + gSequence.append(new textReplaceProcessor(), EVENT_REORDER, getNode("li"), + "change text of HTML li"); + + gSequence.processNext(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="setParent shouldn't be virtual" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=496783">Mozilla Bug 496783</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ul> + <li id="li">item1</li> + </ul> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html b/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html new file mode 100644 index 0000000000..59b9dc9c53 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test HTML li and listitem bullet accessible insertion into editable document</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function addLi(aID) { + this.listNode = getNode(aID); + this.liNode = document.createElement("li"); + this.liNode.textContent = "item"; + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getAccessible, this.liNode), + new invokerChecker(EVENT_REORDER, this.listNode), + ]; + + this.invoke = function addLi_invoke() { + this.listNode.appendChild(this.liNode); + }; + + this.finalCheck = function addLi_finalCheck() { + var tree = { + role: ROLE_LIST, + children: [ + { + role: ROLE_LISTITEM, + children: [ + { + role: ROLE_LISTITEM_MARKER, + name: "1. ", + children: [], + }, + { + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }, + ], + }; + testAccessibleTree(aID, tree); + }; + + this.getID = function addLi_getID() { + return "add li"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + + var gQueue = null; + + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new addLi("list")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body contentEditable="true"> + + <a target="_blank" + title="Wrong list bullet text of accessible for the first numbered HTML:li in CKEditor" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=557795">Mozilla Bug 557795</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ol id="list"> + </ol> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_list_style.html b/accessible/tests/mochitest/treeupdate/test_list_style.html new file mode 100644 index 0000000000..1d9e1c00ca --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_list_style.html @@ -0,0 +1,181 @@ +<html> + +<head> + <title>Test hide/show events for HTMLListBulletAccessibles on list restyle</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="../name.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + /** + * Change list style type to none. + */ + async function hideBullet() { + info("Hide bullet by setting style to none"); + + let liAcc = getAccessible("list_element"); + let bullet = liAcc.firstChild; + + let events = waitForEvents([ + [EVENT_HIDE, bullet], + [EVENT_REORDER, liAcc] + ]); + + getNode("list").style.setProperty("list-style-type", "none"); + + await events; + + is(liAcc.name, "list element", + "Check that first child of LI is not a bullet."); + + dumpTree("list"); + } + + /** + * Change list style type to circles. + */ + async function showBullet() { + info("Show bullet by setting style to circle"); + let liAcc = getAccessible("list_element"); + + let events = waitForEvents([ + [EVENT_SHOW, evt => evt.accessible.parent == liAcc], + [EVENT_REORDER, liAcc] + ]); + + getNode("list").style.setProperty("list-style-type", "circle"); + + await events; + + is(liAcc.name, "â—¦ list element", + "Check that first child of LI is a circle bullet."); + + dumpTree("list"); + } + + /** + * Change list style position. + */ + async function changeBulletPosition() { + info("Change list style position"); + let liAcc = getAccessible("list_element"); + + let events = waitForEvents([ + [EVENT_HIDE, evt => evt.accessible.role == ROLE_LISTITEM_MARKER], + [EVENT_SHOW, evt => evt.accessible.role == ROLE_LISTITEM_MARKER], + [EVENT_REORDER, liAcc] + ]); + + getNode("list").style.setProperty("list-style-position", "inside"); + + await events; + + is(liAcc.name, "â—¦ list element", + "Check that first child of LI is a circle bullet."); + } + + async function changeBulletPositionAndType() { + let events = waitForEvents([ + [EVENT_HIDE, evt => evt.accessible.role == ROLE_LISTITEM_MARKER], + [EVENT_REORDER, evt => evt.accessible.role == ROLE_LISTITEM] + ]); + + let list = getNode("inside-marker-list"); + + // Bug 1513447 - This changes the list type to "none" and the + // position implicitly to "outside". + list.style.setProperty("list-style", "none"); + list.offsetLeft + list.style.setProperty("list-style-type", "telugu"); + + await events; + } + + async function doTest() { + + testAccessibleTree("list", { LIST: [ // ol + { LISTITEM: [ // li + { LISTITEM_MARKER: [ ] }, + { TEXT_LEAF: [] }, + ] }, + ] } ); + + await hideBullet(); + + testAccessibleTree("list", { LIST: [ // ol + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + ] } ); + + await showBullet(); + + testAccessibleTree("list", { LIST: [ // ol + { LISTITEM: [ // li + { LISTITEM_MARKER: [ ] }, + { TEXT_LEAF: [] }, + ] }, + ] } ); + + await changeBulletPosition(); + + testAccessibleTree("list", { LIST: [ // ol + { LISTITEM: [ // li + { LISTITEM_MARKER: [ ] }, + { TEXT_LEAF: [] }, + ] }, + ] } ); + + testAccessibleTree("unmarked-list", { LIST: [ // ol + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + ] } ); + + await changeBulletPositionAndType(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1100602" + title="[e10s] crash in mozilla::a11y::ProxyAccessible::Shutdown()"> + Mozilla Bug 1100602 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ol id="list" style="list-style-type: circle;"> + <li id="list_element">list element</li> + </ol> + + <ol id="unmarked-list" style="list-style: none;"> + <li>list element</li> + </ol> + + <ol id="inside-marker-list" style="list-style-position: inside;"> + <li>list element</li> + </ol> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_listbox.xhtml b/accessible/tests/mochitest/treeupdate/test_listbox.xhtml new file mode 100644 index 0000000000..629c4b0915 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_listbox.xhtml @@ -0,0 +1,181 @@ +<?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 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL listbox hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function insertListitem(aListboxID) + { + this.listboxNode = getNode(aListboxID); + + this.listitemNode = document.createXULElement("richlistitem"); + var label = document.createXULElement("label"); + label.setAttribute("value", "item1"); + this.listitemNode.appendChild(label); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.listitemNode), + new invokerChecker(EVENT_REORDER, this.listboxNode) + ]; + + this.invoke = function insertListitem_invoke() + { + this.listboxNode.insertBefore(this.listitemNode, + this.listboxNode.firstChild); + } + + this.finalCheck = function insertListitem_finalCheck() + { + var tree = + { LISTBOX: [ + { + role: ROLE_RICH_OPTION, + name: "item1" + }, + { + role: ROLE_RICH_OPTION, + name: "item2" + }, + { + role: ROLE_RICH_OPTION, + name: "item3" + }, + { + role: ROLE_RICH_OPTION, + name: "item4" + } + ] }; + testAccessibleTree(this.listboxNode, tree); + } + + this.getID = function insertListitem_getID() + { + return "insert listitem "; + } + } + + function removeListitem(aListboxID) + { + this.listboxNode = getNode(aListboxID); + this.listitemNode = null; + this.listitem; + + function getListitem(aThisObj) + { + return aThisObj.listitem; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getListitem, this), + new invokerChecker(EVENT_REORDER, this.listboxNode) + ]; + + this.invoke = function removeListitem_invoke() + { + this.listitemNode = this.listboxNode.firstChild; + this.listitem = getAccessible(this.listitemNode); + + this.listboxNode.removeChild(this.listitemNode); + } + + this.finalCheck = function removeListitem_finalCheck() + { + var tree = + { LISTBOX: [ + { + role: ROLE_RICH_OPTION, + name: "item2" + }, + { + role: ROLE_RICH_OPTION, + name: "item3" + }, + { + role: ROLE_RICH_OPTION, + name: "item4" + } + ] }; + testAccessibleTree(this.listboxNode, tree); + } + + this.getID = function removeListitem_getID() + { + return "remove listitem "; + } + } + + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTest() + { + var tree = + { LISTBOX: [ + { + role: ROLE_RICH_OPTION, + name: "item2" + }, + { + role: ROLE_RICH_OPTION, + name: "item3" + }, + { + role: ROLE_RICH_OPTION, + name: "item4" + } + ] }; + testAccessibleTree("listbox", tree); + + gQueue = new eventQueue(); + gQueue.push(new insertListitem("listbox")); + gQueue.push(new removeListitem("listbox")); + gQueue.invoke(); // Will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=656225" + title="XUL listbox accessible tree doesn't get updated"> + Mozilla Bug 656225 + </a> + <br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <richlistbox id="listbox"> + <richlistitem><label value="item2"/></richlistitem> + <richlistitem><label value="item3"/></richlistitem> + <richlistitem><label value="item4"/></richlistitem> + </richlistbox> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_menu.xhtml b/accessible/tests/mochitest/treeupdate/test_menu.xhtml new file mode 100644 index 0000000000..44042cc9e7 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_menu.xhtml @@ -0,0 +1,127 @@ +<?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 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL menu hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function openMenu(aID) + { + this.menuNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, this.menuNode) + ]; + + this.invoke = function openMenu_invoke() + { + var tree; + if (LINUX || SOLARIS) { + tree = + { PARENT_MENUITEM: [ ] }; + + } else { + tree = + { PARENT_MENUITEM: [ + { MENUPOPUP: [ ] } + ] }; + } + testAccessibleTree(aID, tree); + + // Show menu. + this.menuNode.open = true; + } + + this.finalCheck = function openMenu_finalCheck() + { + var tree; + if (LINUX || SOLARIS) { + tree = + { PARENT_MENUITEM: [ + { MENUITEM: [ ] }, + { MENUITEM: [ ] } + ] }; + + } else { + tree = + { PARENT_MENUITEM: [ + { MENUPOPUP: [ + { MENUITEM: [ ] }, + { MENUITEM: [ ] } + ] } + ] }; + } + testAccessibleTree(aID, tree); + } + + this.getID = function openMenu_getID() + { + return "open menu " + prettyName(aID); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + gQueue.push(new openMenu("menu")); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292" + title="Ensure accessible children for toolbarbutton types 'menu'"> + Mozilla Bug 249292 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486" + title="Don't force accessible creation for popup children."> + Mozilla Bug 630486 + </a> + <br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menubar> + <menu id="menu" label="menu"> + <menupopup> + <menuitem label="menuitem"/> + <menuitem label="menuitem"/> + </menupopup> + </menu> + </menubar> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml b/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml new file mode 100644 index 0000000000..d3eeb29f78 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml @@ -0,0 +1,141 @@ +<?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 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL button hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function openMenu(aButtonID, aIsTree) + { + var menuItemRole = aIsTree ? ROLE_CHECK_MENU_ITEM : ROLE_MENUITEM; + this.button = getAccessible(aButtonID); + this.menupopup = aIsTree ? this.button.nextSibling : this.button.firstChild; + + var checker = new invokerChecker(EVENT_REORDER, this.menupopup); + this.__proto__ = new synthClick(aButtonID, checker); + + let testButton = popup => { + var children = []; + if (!aIsTree) { + children.push(popup); + } + var tree = { PUSHBUTTON: children }; + testAccessibleTree(this.button, tree); + testAccessibleTree(this.menupop, popup); + } + + this.invoke = function openMenu_invoke() + { + testButton({ MENUPOPUP: [ ] }); + this.__proto__.invoke(); + } + + this.finalCheck = function openMenu_finalCheck() + { + testButton({ MENUPOPUP: [ + { role: menuItemRole, children: [ ] }, + { role: menuItemRole, children: [ ] } + ] }); + + synthesizeKey("KEY_Escape"); + } + + this.getID = function openMenu_getID() + { + return "open menu of the button " + prettyName(aButtonID); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do test + + gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new openMenu("button1")); + gQueue.push(new openMenu("button3")); + + var columnPickerBtn = getAccessible("tree").firstChild.lastChild.previousSibling; + gQueue.push(new openMenu(columnPickerBtn, true)); + gQueue.invoke(); // SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292" + title="Ensure accessible children for toolbarbutton types 'menu'"> + Bug 249292 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486" + title="Don't force accessible creation for popup children"> + Bug 630486 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=722265" + title="Column header selection popup no longer exposed to accessibility APIs"> + Bug 722265 + </a> + <br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <button id="button1" type="menu" label="button"> + <menupopup> + <menuitem label="menuitem"/> + <menuitem label="menuitem"/> + </menupopup> + </button> + + <toolbarbutton id="button3" type="menu" label="toolbarbutton"> + <menupopup> + <menuitem label="menuitem"/> + <menuitem label="menuitem"/> + </menupopup> + </toolbarbutton> + + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="another column"/> + </treecols> + <treechildren/> + </tree> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_optgroup.html b/accessible/tests/mochitest/treeupdate/test_optgroup.html new file mode 100644 index 0000000000..943b73be43 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_optgroup.html @@ -0,0 +1,122 @@ +<!DOCTYPE html> +<html> +<head> + <title>Add and remove optgroup test</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + function addOptGroup(aID) { + this.selectNode = getNode(aID); + this.select = getAccessible(this.selectNode); + this.selectList = this.select.firstChild; + + this.invoke = function addOptGroup_invoke() { + var optGroup = document.createElement("optgroup"); + for (let i = 0; i < 2; i++) { + var opt = document.createElement("option"); + opt.value = i; + opt.text = "Option: Value " + i; + + optGroup.appendChild(opt); + } + + this.selectNode.add(optGroup, null); + var option = document.createElement("option"); + this.selectNode.add(option, null); + }; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.selectList), + ]; + + this.finalCheck = function addOptGroup_finalCheck() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [ + { GROUPING: [ + { COMBOBOX_OPTION: [ ] }, + { COMBOBOX_OPTION: [ ] }, + ]}, + { COMBOBOX_OPTION: [] }, + ] }, + ] }; + testAccessibleTree(this.select, tree); + }; + + this.getID = function addOptGroup_getID() { + return "test optgroup's insertion into a select"; + }; + } + + function removeOptGroup(aID) { + this.selectNode = getNode(aID); + this.select = getAccessible(this.selectNode); + this.selectList = this.select.firstChild; + + this.invoke = function removeOptGroup_invoke() { + this.option1Node = this.selectNode.firstChild.firstChild; + this.selectNode.firstChild.remove(); + }; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.selectList), + ]; + + this.finalCheck = function removeOptGroup_finalCheck() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [ + { COMBOBOX_OPTION: [] }, + ] }, + ] }; + testAccessibleTree(this.select, tree); + is(isAccessible(this.option1Node), false, "removed option shouldn't be accessible anymore!"); + }; + + this.getID = function removeOptGroup_getID() { + return "test optgroup's removal from a select"; + }; + } + + // gA11yEventDumpToConsole = true; + + function doTest() { + const gQueue = new eventQueue(); + + gQueue.push(new addOptGroup("select")); + gQueue.push(new removeOptGroup("select")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=616452" + title="Bug 616452 - Dynamically inserted select options aren't reflected in accessible tree"> + Bug 616452</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="select"></select> + + <div id="debug"/> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_recreation.html b/accessible/tests/mochitest/treeupdate/test_recreation.html new file mode 100644 index 0000000000..d403b0890e --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_recreation.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible recreation</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="../role.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + + async function doTest() { + let events, msg; + + msg = "Assign a 'button' role to a span"; + events = waitForOrderedEvents( + [[EVENT_HIDE], [EVENT_SHOW, "span"], [EVENT_REORDER, "container"]], msg); + document.getElementById("span").setAttribute("role", "button"); + await events; + + msg = "Remove the 'button' role from a span"; + events = waitForOrderedEvents( + [[EVENT_HIDE, "span"], [EVENT_SHOW], [EVENT_REORDER, "container"]], msg); + document.getElementById("span").removeAttribute("role"); + await events; + + msg = "Assign a 'button' role to a div"; + events = waitForOrderedEvents( + [[EVENT_HIDE, "div1"], [EVENT_SHOW, "div1"], [EVENT_REORDER, "container"]], msg); + document.getElementById("div1").setAttribute("role", "button"); + await events; + + msg = "Change a password field to a text field"; + events = waitForOrderedEvents( + [[EVENT_HIDE, "password"], [EVENT_SHOW, "password"], [EVENT_REORDER, "container"]], msg); + document.getElementById("password").setAttribute("type", "text"); + await events; + + msg = "Change a text field to a password field"; + events = waitForOrderedEvents( + [[EVENT_HIDE, "text"], [EVENT_SHOW, "text"], [EVENT_REORDER, "container"]], msg); + document.getElementById("text").setAttribute("type", "password"); + await events; + + msg = "Add aria-label to a span"; + ok(!isAccessible("span2"), "span2 has no accessible"); + events = waitForOrderedEvents( + [[EVENT_SHOW, "span2"], [EVENT_REORDER, "container"]], msg); + document.getElementById("span2").setAttribute("aria-label", "label"); + await events; + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="container"> + <span id="span">span</span> + <div id="div1">div</div> + <a id="anchor">anchor</a> + <div id="div3" role="listbox">list</div> + <input type="password" id="password"/> + <input type="text" id="text"/> + <span id="span2"></span> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_select.html b/accessible/tests/mochitest/treeupdate/test_select.html new file mode 100644 index 0000000000..eabb64f80f --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_select.html @@ -0,0 +1,191 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML select options test</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + function addOptions(aID) { + this.selectNode = getNode(aID); + this.select = getAccessible(this.selectNode); + this.selectList = this.select.firstChild; + + this.invoke = function addOptions_invoke() { + for (let i = 0; i < 2; i++) { + var opt = document.createElement("option"); + opt.value = i; + opt.text = "Option: Value " + i; + + this.selectNode.add(opt, null); + } + }; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.selectList), + ]; + + this.finalCheck = function addOptions_finalCheck() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [ + { COMBOBOX_OPTION: [ ] }, + { COMBOBOX_OPTION: [ ] }, + ] }, + ] }; + testAccessibleTree(this.select, tree); + }; + + this.getID = function addOptions_getID() { + return "test elements insertion into a select"; + }; + } + + function removeOptions(aID) { + this.selectNode = getNode(aID); + this.select = getAccessible(this.selectNode); + this.selectList = this.select.firstChild; + + this.invoke = function removeOptions_invoke() { + while (this.selectNode.length) + this.selectNode.remove(0); + }; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.selectList), + ]; + + this.finalCheck = function removeOptions_finalCheck() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [] }, + ] }; + testAccessibleTree(this.select, tree); + }; + + this.getID = function removeptions_getID() { + return "test elements removal from a select"; + }; + } + + /** + * Setting role=option on option makes the accessible recreate. + */ + function setRoleOnOption() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, "s2_o"), + new invokerChecker(EVENT_SHOW, "s2_o"), + ]; + + this.invoke = function setRoleOnOption_setRole() { + getNode("s2_o").setAttribute("role", "option"); + }; + + this.finalCheck = function() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [ + { COMBOBOX_OPTION: [ ] }, + ] }, + ] }; + testAccessibleTree("s2", tree); + }; + + this.getID = function removeptions_getID() { + return "setting role=option on select option"; + }; + } + + /** + * Setting multiple on select makes the accessible recreate. + */ + function setMultipleOnSelect() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, "s3"), + new invokerChecker(EVENT_SHOW, "s3"), + ]; + + this.invoke = function setRoleOnOption_setRole() { + getNode("s3").multiple = true; + }; + + this.finalCheck = function() { + var tree = + { LISTBOX: [ + { OPTION: [ ] }, + ] }; + testAccessibleTree("s3", tree); + }; + + this.getID = function removeptions_getID() { + return "setting multiple on select element"; + }; + } + + + /** + * Removing size on select makes the accessible recreate. + */ + function removeSizeFromSelect() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, "s4"), + new invokerChecker(EVENT_SHOW, "s4"), + ]; + + this.invoke = function setRoleOnOption_setRole() { + getNode("s4").removeAttribute("size"); + }; + + this.finalCheck = function() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [ + { COMBOBOX_OPTION: [ ] }, + ] }, + ] }; + testAccessibleTree("s4", tree); + }; + + this.getID = function removeptions_getID() { + return "removing size from select element"; + }; + } + + function doTest() { + const gQueue = new eventQueue(); + + gQueue.push(new addOptions("select")); + gQueue.push(new removeOptions("select")); + gQueue.push(new setRoleOnOption()); + gQueue.push(new setMultipleOnSelect()); + gQueue.push(new removeSizeFromSelect()); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="select"></select> + <select id="s2"><option id="s2_o"></option></select> + <select id="s3"><option></option></select> + <select id="s4" size="3"><option></option></select> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_shadow_slots.html b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html new file mode 100644 index 0000000000..27baef0e4a --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html @@ -0,0 +1,554 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test shadow roots with slots</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="../role.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + async function _dynamicShadowTest(name, mutationFunc, expectedTree, reorder_targets) { + info(name); + + let container = getNode(name); + let host = container.querySelector('.host'); + + document.body.offsetTop; + let event = reorder_targets ? + waitForEvents(reorder_targets.map(target => [EVENT_REORDER, target, name])) : + waitForEvent(EVENT_REORDER, host, name); + + mutationFunc(container, host); + + await event; + + testAccessibleTree(container, expectedTree); + + return true; + } + + async function attachFlatShadow() { + await _dynamicShadowTest("attachFlatShadow", + (container, host) => { + host.attachShadow({ mode: "open" }) + .appendChild(container.querySelector('.shadowtree').content.cloneNode(true)); + }, { SECTION: [{ SECTION: [{ name: "red"} ] }] }); + } + + async function attachOneDeepShadow() { + await _dynamicShadowTest("attachOneDeepShadow", + (container, host) => { + host.attachShadow({ mode: "open" }) + .appendChild(container.querySelector('.shadowtree').content.cloneNode(true)); + }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "red"} ] }] }] }); + } + + async function changeSlotFlat() { + await _dynamicShadowTest("changeSlotFlat", + (container, host) => { + container.querySelector('.red').removeAttribute('slot'); + container.querySelector('.green').setAttribute('slot', 'myslot'); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + async function changeSlotOneDeep() { + await _dynamicShadowTest("changeSlotOneDeep", + (container, host) => { + container.querySelector('.red').removeAttribute('slot'); + container.querySelector('.green').setAttribute('slot', 'myslot'); + }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "green"} ] }] }] }, ["shadowdiv"]); + } + + // Nested roots and slots + async function changeSlotNested() { + await _dynamicShadowTest("changeSlotNested", + (container, host) => { + testAccessibleTree(getNode("changeSlotNested"), + { SECTION: [{ SECTION: [{ SECTION: [{ name: "red"} ] }] }] }); + container.querySelector('.red').removeAttribute('slot'); + container.querySelector('.green').setAttribute('slot', 'myslot'); + }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "green"} ] }] }] }, ["shadowdiv"]); + } + + async function changeSlotSingleChild() { + await _dynamicShadowTest("changeSlotSingleChild", + (container, host) => { + container.querySelector('.red').setAttribute('slot', 'invalid'); + }, { SECTION: [{ SECTION: [] }] }); + } + + async function changeSlotNoShadow() { + await _dynamicShadowTest("changeSlotNoShadow", + (container, host) => { + // Make sure changing the slot attribute doesn't remove an element + // even when it remains in the flat tree. + container.querySelector('.red').setAttribute('slot', 'invalid'); + // We need a reorder to know when we're done here, so remove another + // child. + container.querySelector('.green').remove(); + }, { SECTION: [{ SECTION: [{ name: "red"} ] }] }); + } + + // Dynamic mutations to both shadow root and shadow host subtrees + // testing/web-platform/tests/css/css-scoping/shadow-assign-dynamic-001.html + async function assignSlotDynamic() { + await _dynamicShadowTest("assignSlotDynamic", + (container, host) => { + host.shadowRoot.appendChild(container.querySelector('.shadowtree').content.cloneNode(true)); + host.appendChild(container.querySelector('.lighttree').content.cloneNode(true)); + }, { SECTION: [{ SECTION: [{ name: "slot1"}, { name: "slot2" } ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-001.html + async function shadowFallbackDynamic_1() { + await _dynamicShadowTest("shadowFallbackDynamic_1", + (container, host) => { + host.firstElementChild.remove(); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-002.html + async function shadowFallbackDynamic_2() { + await _dynamicShadowTest("shadowFallbackDynamic_2", + (container, host) => { + host.firstElementChild.removeAttribute("slot"); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-003.html + async function shadowFallbackDynamic_3() { + await _dynamicShadowTest("shadowFallbackDynamic_3", + (container, host) => { + host.appendChild(container.querySelector(".lighttree").content.cloneNode(true)); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-004.html + async function shadowFallbackDynamic_4() { + await _dynamicShadowTest("shadowFallbackDynamic_4", + (container, host) => { + host.shadowRoot.insertBefore( + container.querySelector(".moreshadowtree"). + content.cloneNode(true), host.shadowRoot.firstChild); + }, { SECTION: [{ SECTION: [{ name: "slotparent2", children: [{ name: "green"} ] }, { name: "slotparent1" } ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-004.html + // This tests a case when the the slotted element would remain in the same accessible container + async function shadowFallbackDynamic_4_1() { + await _dynamicShadowTest("shadowFallbackDynamic_4_1", + (container, host) => { + host.shadowRoot.insertBefore( + container.querySelector(".moreshadowtree"). + content.cloneNode(true), host.shadowRoot.firstChild); + }, { SECTION: [{ SECTION: [ { name: "green"}, { SEPARATOR: [] } ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-005.html + async function shadowFallbackDynamic_5() { + await _dynamicShadowTest("shadowFallbackDynamic_5", + (container, host) => { + host.firstElementChild.setAttribute("slot", "myotherslot"); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-002.html + async function shadowReassignDynamic_2() { + await _dynamicShadowTest("shadowReassignDynamic_2", + (container, host) => { + host.shadowRoot.querySelector("slot").setAttribute("name", "myslot"); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-003.html + async function shadowReassignDynamic_3() { + await _dynamicShadowTest("shadowReassignDynamic_3", + (container, host) => { + testAccessibleTree(container, { SECTION: [{ SECTION: [{ name: "green"}, { name: "red", children: [ { PUSHBUTTON: [] }]} ] }] }); + host.shadowRoot.querySelector("slot[name]").removeAttribute("name"); + + }, { SECTION: [{ SECTION: [{ name: "green", children: [ { PUSHBUTTON: [] }]}, { name: "red"} ] }] }, + [evt => evt.accessible.name == "green", evt => evt.accessible.name == "red"]); + } + + // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-004.html + async function shadowReassignDynamic_4() { + await _dynamicShadowTest("shadowReassignDynamic_4", + (container, host) => { + host.shadowRoot.getElementById("slot").remove(); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + function shadowProcessInvalidation() { + testAccessibleTree("shadowProcessInvalidation", + { SECTION: [{ + SECTION: [{ + SECTION: [{ TEXT_LEAF: { name: "Hello "} }, + { TEXT: [{ TEXT_LEAF: { name: "World"} }] }, + { PUSHBUTTON: { name: "World"} }] + }] + }] + }); + } + + async function justAttachShadow() { + await _dynamicShadowTest("justAttachShadow", + (container, host) => { + host.attachShadow({ mode: "open" }); + }, { SECTION: [{ SECTION: [] }] }); + } + + async function doTest() { + await attachFlatShadow(); + + await attachOneDeepShadow(); + + await changeSlotFlat(); + + await changeSlotOneDeep(); + + await changeSlotNested(); + + await changeSlotSingleChild(); + + await changeSlotNoShadow(); + + await assignSlotDynamic(); + + await shadowFallbackDynamic_1(); + + await shadowFallbackDynamic_2(); + + await shadowFallbackDynamic_3(); + + await shadowFallbackDynamic_4(); + + await shadowFallbackDynamic_4_1(); + + await shadowFallbackDynamic_5(); + + await shadowReassignDynamic_2(); + + await shadowReassignDynamic_3(); + + await shadowReassignDynamic_4(); + + shadowProcessInvalidation(); + + await justAttachShadow(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="attachFlatShadow"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <slot name="myslot">FAIL</slot> + </template> + <section class="host"> + <div style="background: green" aria-label="green"></div> + <div style="background: red" aria-label="red" slot="myslot"></div> + </section> + </div> + + <div id="attachOneDeepShadow"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <div id="shadowdiv"> + <slot name="myslot">FAIL</slot> + </div> + </template> + <section class="host"> + <div style="background: green" aria-label="green"></div> + <div style="background: red" aria-label="red" slot="myslot"></div> + </section> + </div> + + <div id="changeSlotFlat"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <slot name="myslot">FAIL</slot> + </template> + <section class="host"> + <div class="green" style="background: green" aria-label="green"></div> + <div class="red" style="background: red" aria-label="red" slot="myslot"></div> + </section> + <script> + document.querySelector("#changeSlotFlat > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#changeSlotFlat > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="changeSlotOneDeep"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <div id="shadowdiv"> + <slot name="myslot">FAIL</slot> + </div> + </template> + <section class="host"> + <div class="green" style="background: green" aria-label="green"></div> + <div class="red" style="background: red" aria-label="red" slot="myslot"></div> + </section> + <script> + document.querySelector("#changeSlotOneDeep > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#changeSlotOneDeep > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="changeSlotNested"> + <template class="shadowtree outer"> + <div id="shadowdiv"> + <slot name="myslot">FAIL</slot> + </div> + </template> + <template class="shadowtree inner"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <slot>FAIL</slot> + </template> + <section class="host"> + <div class="green" style="background: green" aria-label="green"></div> + <div class="red" style="background: red" aria-label="red" slot="myslot"></div> + </section> + <script> + (function foo() { + let outerShadow = + document.querySelector("#changeSlotNested > .host"). + attachShadow({ mode: "open" }); + outerShadow.appendChild( + document.querySelector("#changeSlotNested > .shadowtree.outer"). + content.cloneNode(true)); + let innerShadow = + outerShadow.querySelector("#shadowdiv"). + attachShadow({ mode: "open" }); + innerShadow.appendChild( + document.querySelector("#changeSlotNested > .shadowtree.inner"). + content.cloneNode(true)); + })(); + </script> + </div> + + <div id="changeSlotSingleChild"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <slot></slot> + </template> + <section class="host"> + <div class="red" style="background: red" aria-label="red"></div> + </section> + <script> + document.querySelector("#changeSlotSingleChild > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#changeSlotSingleChild > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="changeSlotNoShadow"> + <section class="host"> + <div class="red" style="background: red; width: 100px; height: 100px;" aria-label="red"></div> + <div class="green" style="background: green; width: 100px; height: 100px;" aria-label="green"></div> + </section> + </div> + + <div id="assignSlotDynamic"> + <template class="shadowtree"> + <style>::slotted(div) { width: 50px; height: 100px }</style> + <slot name="slot1">FAIL</slot> + <slot name="slot2">FAIL</slot> + </template> + <template class="lighttree"> + <div aria-label="slot1" slot="slot1"></div> + <div aria-label="slot2" slot="slot2"></div> + </template> + <section class="host"></section> + <script> + document.querySelector("#assignSlotDynamic > .host").attachShadow({ mode: "open" }); + </script> + </div> + + <div id="shadowFallbackDynamic_1"> + <template class="shadowtree"> + <slot name="myslot"> + <div aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </slot> + </template> + <section class="host"><span slot="myslot">FAIL</span></section> + <script> + document.querySelector("#shadowFallbackDynamic_1 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_1 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowFallbackDynamic_2"> + <template class="shadowtree"> + <slot name="myslot"> + <div aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </slot> + </template> + <section class="host"><span slot="myslot">FAIL</span></section> + <script> + document.querySelector("#shadowFallbackDynamic_2 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_2 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowFallbackDynamic_3"> + <template class="shadowtree"> + <slot name="myslot">FAIL</slot> + </template> + <template class="lighttree"> + <div aria-label="green" slot="myslot" style="width: 100px; height: 100px; background: green"></div> + </template> + <section class="host"></section> + <script> + document.querySelector("#shadowFallbackDynamic_3 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_3 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowFallbackDynamic_4"> + <template class="shadowtree"> + <div aria-label="slotparent1"><slot name="myslot"></slot></div> + </template> + <template class="moreshadowtree"> + <div aria-label="slotparent2"><slot name="myslot">FAIL</slot></div> + </template> + <section class="host"> + <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </section> + <script> + document.querySelector("#shadowFallbackDynamic_4 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_4 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowFallbackDynamic_4_1"> + <template class="shadowtree"> + <hr> + <slot name="myslot"></slot> + </template> + <template class="moreshadowtree"> + <slot name="myslot">FAIL</slot> + </template> + <section class="host"> + <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </section> + <script> + document.querySelector("#shadowFallbackDynamic_4_1 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_4_1 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowFallbackDynamic_5"> + <template class="shadowtree"> + <slot name="myslot"></slot> + <slot name="myotherslot">FAIL</slot> + </template> + <section class="host"> + <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </section> + <script> + document.querySelector("#shadowFallbackDynamic_5 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_5 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowReassignDynamic_2"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <slot>FAIL</slot> + </template> + <section class="host"> + <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </section> + <script> + document.querySelector("#shadowReassignDynamic_2 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowReassignDynamic_2 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowReassignDynamic_3"> + <template class="shadowtree"> + <div aria-label="green"><slot name="nomatch"></slot></div> + <div aria-label="red"><slot></slot></div> + </template> + <section class="host"> + <div role="button"></div> + </section> + <script> + document.querySelector("#shadowReassignDynamic_3 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowReassignDynamic_3 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowReassignDynamic_4"> + <template class="shadowtree"> + <style>::slotted(div),div { width: 100px; height: 100px }</style> + <slot id="slot"></slot> + <slot> + <div aria-label="red" style="background: red"></div> + </slot> + </template> + <section class="host"> + <div aria-label="green" style="background: green"></div> + </section> + <script> + document.querySelector("#shadowReassignDynamic_4 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowReassignDynamic_4 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowProcessInvalidation"> + <template class="shadowtree"> + <div id="shadowdiv"> + <slot></slot> + </div> + </template> + <section class="host">Hello <span id="c">World</span><button aria-labelledby="c"></button></section> + <script> + document.querySelector("#shadowProcessInvalidation > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowProcessInvalidation > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="justAttachShadow"> + <section class="host"> + <button></button> + </section> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml b/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml new file mode 100644 index 0000000000..ad8aebf812 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml @@ -0,0 +1,131 @@ +<?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 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL tree hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + function setXULTreeView(aTreeID, aTreeView) + { + this.treeNode = getNode(aTreeID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.treeNode) + ]; + + this.invoke = function loadXULTree_invoke() + { + this.treeNode.view = aTreeView; + }; + + this.getID = function loadXULTree_getID() + { + return "Load XUL tree " + prettyName(aTreeID); + }; + } + + function removeTree(aID) + { + this.tree = getAccessible(aID); + this.lastItem = null; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, document) + ]; + + this.invoke = function invoke() + { + this.lastItem = getAccessible(aID).lastChild; + this.lastCell = this.lastItem.lastChild; + getNode(aID).remove(); + }; + + this.check = function check(aEvent) + { + testIsDefunct(this.tree, aID); + testIsDefunct(this.lastItem, "last item of " + aID); + if (this.lastCell) { + testIsDefunct(this.lastCell, "last item cell of " + aID); + } + }; + + this.getID = function getID() + { + return "Remove tree from DOM"; + }; + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "debug"; + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new setXULTreeView("tree", new nsTreeTreeView())); + gQueue.push(new removeTree("tree")); + + gQueue.push(new setXULTreeView("treetable", new nsTreeTreeView())); + gQueue.push(new removeTree("treetable")); + + gQueue.invoke(); // Will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727" + title="Reorganize implementation of XUL tree accessibility"> + Bug 503727 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <tree id="treetable" flex="1"> + <treecols> + <treecol id="col1" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="column 2"/> + </treecols> + <treechildren/> + </tree> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/treeupdate/test_table.html b/accessible/tests/mochitest/treeupdate/test_table.html new file mode 100644 index 0000000000..50fac91757 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_table.html @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<html> +<head> + <title>Table update tests</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + function appendCaption(aTableID) { + this.invoke = function appendCaption_invoke() { + // append a caption, it should appear as a first element in the + // accessible tree. + var caption = document.createElement("caption"); + caption.textContent = "table caption"; + getNode(aTableID).appendChild(caption); + }; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aTableID), + ]; + + this.finalCheck = function appendCaption_finalCheck() { + var tree = + { TABLE: [ + { CAPTION: [ + { TEXT_LEAF: [] }, + ] }, + { ROW: [ + { CELL: [ {TEXT_LEAF: [] }]}, + { CELL: [ {TEXT_LEAF: [] }]}, + ] }, + ] }; + testAccessibleTree(aTableID, tree); + }; + + this.getID = function appendCaption_getID() { + return "append caption"; + }; + } + + function doTest() { + const gQueue = new eventQueue(); + gQueue.push(new appendCaption("table")); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <table id="table"> + <tr> + <td>cell1</td> + <td>cell2</td> + </tr> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_textleaf.html b/accessible/tests/mochitest/treeupdate/test_textleaf.html new file mode 100644 index 0000000000..f0181dd754 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_textleaf.html @@ -0,0 +1,167 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible recreation</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function textLeafUpdate(aID, aIsTextLeafLinkable) { + this.node = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.node.parentNode), + ]; + + this.finalCheck = function textLeafUpdate_finalCheck() { + var textLeaf = getAccessible(this.node).firstChild; + is(textLeaf.actionCount, (aIsTextLeafLinkable ? 1 : 0), + "Wrong action numbers!"); + }; + } + + function setOnClickAttr(aID) { + var node = getNode(aID); + node.setAttribute("onclick", "alert(3);"); + var textLeaf = getAccessible(node).firstChild; + is(textLeaf.actionCount, 1, "setOnClickAttr: wrong action numbers!"); + } + + function removeOnClickAttr(aID) { + var node = getNode(aID); + node.removeAttribute("onclick"); + var textLeaf = getAccessible(node).firstChild; + is(textLeaf.actionCount, 0, + "removeOnClickAttr: wrong action numbers!"); + } + + function setOnClickNRoleAttrs(aID) { + this.__proto__ = new textLeafUpdate(aID, true); + + this.invoke = function setOnClickAttr_invoke() { + this.node.setAttribute("role", "link"); + this.node.setAttribute("onclick", "alert(3);"); + }; + + this.getID = function setOnClickAttr_getID() { + return "make " + prettyName(aID) + " linkable"; + }; + } + + function removeTextData(aID, aRole) { + this.containerNode = getNode(aID); + this.textNode = this.containerNode.firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function removeTextData_invoke() { + var tree = { + role: aRole, + children: [ + { + role: ROLE_TEXT_LEAF, + name: "text", + }, + ], + }; + testAccessibleTree(this.containerNode, tree); + + this.textNode.data = ""; + }; + + this.finalCheck = function removeTextData_finalCheck() { + var tree = { + role: aRole, + children: [], + }; + testAccessibleTree(this.containerNode, tree); + }; + + this.getID = function removeTextData_finalCheck() { + return "remove text data of text node inside '" + aID + "'."; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + // adds onclick on element, text leaf should inherit its action + setOnClickAttr("div"); + // remove onclick attribute, text leaf shouldn't have any action + removeOnClickAttr("div"); + + // Call rest of event tests. + gQueue = new eventQueue(); + + // set onclick attribute making span accessible, it's inserted into tree + // and adopts text leaf accessible, text leaf should have an action + gQueue.push(new setOnClickNRoleAttrs("span")); + + // text data removal of text node should remove its text accessible + gQueue.push(new removeTextData("p", ROLE_PARAGRAPH)); + gQueue.push(new removeTextData("pre", ROLE_TEXT_CONTAINER)); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Clean up the code of accessible initialization and binding to the tree" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=545465"> + Mozilla Bug 545465 + </a> + <a target="_blank" + title="Make sure accessible tree is correct when rendered text is changed" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652"> + Mozilla Bug 625652 + </a> + <a target="_blank" + title="Remove text accesible getting no text inside a preformatted area" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=706335"> + Mozilla Bug 706335 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="container"> + <div id="div">div</div> + <span id="span">span</span> + </div> + + <p id="p">text</p> + <pre id="pre">text</pre> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml b/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml new file mode 100644 index 0000000000..dba83b2267 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml @@ -0,0 +1,75 @@ +<?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 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL tooltip test"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../promisified-events.js" /> + + <script type="application/javascript"> + <![CDATA[ + + async function doTest() { + let tooltip = document.getElementById("tooltip"); + + testAccessibleTree("tooltip-container", { GROUPING: [ + ] }); + + let shown = waitForEvent(EVENT_SHOW, tooltip); + tooltip.openPopup(); + await shown; + + testAccessibleTree("tooltip-container", + { GROUPING: [ + { TOOLTIP: [] }, + { STATICTEXT: [] }, + ] }); + + let hidden = waitForEvent(EVENT_HIDE, tooltip); + tooltip.hidePopup(); + await hidden; + + testAccessibleTree("tooltip-container", { GROUPING: [] }); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1652211" + title="Added anonymous tooltip to mochitest docs messes with text"> + Bug 1652211 + </a> + <br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1" role="group" id="tooltip-container"> + <tooltip id="tooltip"> + <description class="tooltip-label" value="hello world"/> + </tooltip> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_visibility.html b/accessible/tests/mochitest/treeupdate/test_visibility.html new file mode 100644 index 0000000000..4107832b3e --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_visibility.html @@ -0,0 +1,411 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Style visibility tree update test</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + /** + * Hide parent while child stays visible. + */ + function test1(aContainerID, aParentID, aChildID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode(aParentID)), + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ + { SECTION: [ + { SECTION: [ + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aParentID).style.visibility = "hidden"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ + { SECTION: [ + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "hide parent while child stays visible"; + }; + } + + /** + * Hide grand parent while its children stay visible. + */ + function test2(aContainerID, aGrandParentID, aChildID, aChild2ID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode(aGrandParentID)), + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_SHOW, getNode(aChild2ID)), + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ // container + { SECTION: [ // grand parent + { SECTION: [ + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + { SECTION: [ // child2 + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aGrandParentID).style.visibility = "hidden"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ // container + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + { SECTION: [ // child2 + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "hide grand parent while its children stay visible"; + }; + } + + /** + * Change container style, hide parents while their children stay visible. + */ + function test3(aContainerID, aParentID, aParent2ID, aChildID, aChild2ID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode(aParentID)), + new invokerChecker(EVENT_HIDE, getNode(aParent2ID)), + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_SHOW, getNode(aChild2ID)), + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ // container + { SECTION: [ // parent + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + ] }, + { SECTION: [ // parent2 + { SECTION: [ // child2 + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aContainerID).style.color = "red"; + getNode(aParentID).style.visibility = "hidden"; + getNode(aParent2ID).style.visibility = "hidden"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ // container + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + { SECTION: [ // child2 + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "change container style, hide parents while their children stay visible"; + }; + } + + /** + * Change container style and make visible child inside the table. + */ + function test4(aContainerID, aChildID) { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_REORDER, getNode(aChildID).parentNode), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ + { TABLE: [ + { ROW: [ + { CELL: [ ] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aContainerID).style.color = "red"; + getNode(aChildID).style.visibility = "visible"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ + { TABLE: [ + { ROW: [ + { CELL: [ + { SECTION: [ + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "change container style, make visible child insdie the table"; + }; + } + + /** + * Hide subcontainer while child inside the table stays visible. + */ + function test5(aContainerID, aSubContainerID, aChildID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode(aSubContainerID)), + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ // container + { SECTION: [ // subcontainer + { TABLE: [ + { ROW: [ + { CELL: [ + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aSubContainerID).style.visibility = "hidden"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ // container + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "hide subcontainer while child inside the table stays visible"; + }; + } + + /** + * Hide subcontainer while its child and child inside the nested table stays visible. + */ + function test6(aContainerID, aSubContainerID, aChildID, aChild2ID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode(aSubContainerID)), + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_SHOW, getNode(aChild2ID)), + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ // container + { SECTION: [ // subcontainer + { TABLE: [ + { ROW: [ + { CELL: [ + { TABLE: [ // nested table + { ROW: [ + { CELL: [ + { SECTION: [ // child + { TEXT_LEAF: [] } ]} ]} ]} ]} ]} ]} ]}, + { SECTION: [ // child2 + { TEXT_LEAF: [] } ]} ]} ]}; + + testAccessibleTree(aContainerID, tree); + + // invoke + getNode(aSubContainerID).style.visibility = "hidden"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ // container + { SECTION: [ // child + { TEXT_LEAF: [] } ]}, + { SECTION: [ // child2 + { TEXT_LEAF: [] } ]} ]}; + + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "hide subcontainer while its child and child inside the nested table stays visible"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new test1("t1_container", "t1_parent", "t1_child")); + gQueue.push(new test2("t2_container", "t2_grandparent", "t2_child", "t2_child2")); + gQueue.push(new test3("t3_container", "t3_parent", "t3_parent2", "t3_child", "t3_child2")); + gQueue.push(new test4("t4_container", "t4_child")); + gQueue.push(new test5("t5_container", "t5_subcontainer", "t5_child")); + gQueue.push(new test6("t6_container", "t6_subcontainer", "t6_child", "t6_child2")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Develop a way to handle visibility style" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125"> + Mozilla Bug 606125 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- hide parent while child stays visible --> + <div id="t1_container"> + <div id="t1_parent"> + <div id="t1_child" style="visibility: visible">text</div> + </div> + </div> + + <!-- hide grandparent while its children stay visible --> + <div id="t2_container"> + <div id="t2_grandparent"> + <div id="t2_parent"> + <div id="t2_child" style="visibility: visible">text</div> + <div id="t2_child2" style="visibility: visible">text</div> + </div> + </div> + </div> + + <!-- change container style, hide parents while their children stay visible --> + <div id="t3_container"> + <div id="t3_parent"> + <div id="t3_child" style="visibility: visible">text</div> + </div> + <div id="t3_parent2"> + <div id="t3_child2" style="visibility: visible">text</div> + </div> + </div> + + <!-- change container style, show child inside the table --> + <div id="t4_container"> + <table> + <tr> + <td> + <div id="t4_child" style="visibility: hidden;">text</div> + </td> + </tr> + </table> + </div> + + <!-- hide subcontainer while child inside the table stays visible --> + <div id="t5_container"> + <div id="t5_subcontainer"> + <table> + <tr> + <td> + <div id="t5_child" style="visibility: visible;">text</div> + </td> + </tr> + </table> + </div> + </div> + + <!-- hide subcontainer while its child and child inside the nested table stays visible --> + <div id="t6_container"> + <div id="t6_subcontainer"> + <table> + <tr> + <td> + <table> + <tr> + <td> + <div id="t6_child" style="visibility: visible;">text</div> + </td> + </tr> + </table> + </td> + </tr> + </table> + <div id="t6_child2" style="visibility: visible">text</div> + </div> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_whitespace.html b/accessible/tests/mochitest/treeupdate/test_whitespace.html new file mode 100644 index 0000000000..ebb199cfbe --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_whitespace.html @@ -0,0 +1,200 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Whitespace text accessible creation/destruction</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="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + /** + * Middle image accessible removal results in text accessible removal. + * + * Before: + * DOM: whitespace img1 whitespace img2 whitespace img3 whitespace, + * a11y: img1 whitespace img2 whitespace img3 + * After: + * DOM: whitespace img1 whitespace whitespace img3 whitespace, + * a11y: img1 whitespace img3 + */ + function removeImg() { + this.containerNode = getNode("container1"); + this.imgNode = getNode("img1"); + this.img = getAccessible(this.imgNode); + this.text = this.img.nextSibling; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.img), + new invokerChecker(EVENT_HIDE, this.text), + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.finalCheck = function textLeafUpdate_finalCheck() { + var tree = + { SECTION: [ + { GRAPHIC: [] }, + { TEXT_LEAF: [] }, + { GRAPHIC: [] }, + ] }; + + testAccessibleTree(this.containerNode, tree); + }; + + this.invoke = function setOnClickAttr_invoke() { + var tree = + { SECTION: [ + { GRAPHIC: [] }, + { TEXT_LEAF: [] }, + { GRAPHIC: [] }, + { TEXT_LEAF: [] }, + { GRAPHIC: [] }, + ] }; + + testAccessibleTree(this.containerNode, tree); + + this.containerNode.removeChild(this.imgNode); + }; + + this.getID = function setOnClickAttr_getID() { + return "remove middle img"; + }; + } + + /** + * Append image making the whitespace visible and thus accessible. + * Note: images and whitespaces are on different leves of accessible trees, + * so that image container accessible update doesn't update the tree + * of whitespace container. + * + * Before: + * DOM: whitespace emptylink whitespace linkwithimg whitespace + * a11y: emptylink linkwithimg + * After: + * DOM: whitespace linkwithimg whitespace linkwithimg whitespace + * a11y: linkwithimg whitespace linkwithimg + */ + function insertImg() { + this.containerNode = getNode("container2"); + this.topNode = this.containerNode.parentNode; + this.textNode = this.containerNode.nextSibling; + this.imgNode = document.createElement("img"); + this.imgNode.setAttribute("src", "../moz.png"); + + this.eventSeq = [ + new asyncInvokerChecker(EVENT_SHOW, getAccessible, this.textNode), + new asyncInvokerChecker(EVENT_SHOW, getAccessible, this.imgNode), + new orderChecker(), + new invokerChecker(EVENT_REORDER, this.topNode), + ]; + + this.invoke = function insertImg_invoke() { + var tree = + { SECTION: [ + { LINK: [] }, + { LINK: [ + { GRAPHIC: [] }, + ] }, + ] }; + + testAccessibleTree(this.topNode, tree); + + this.containerNode.appendChild(this.imgNode); + }; + + this.finalCheck = function insertImg_finalCheck() { + var tree = + { SECTION: [ + { LINK: [ + { GRAPHIC: [ ] }, + ] }, + { TEXT_LEAF: [ ] }, + { LINK: [ + { GRAPHIC: [ ] }, + ] }, + ] }; + + testAccessibleTree(this.topNode, tree); + }; + + this.getID = function appendImg_getID() { + return "insert img into internal container"; + }; + } + + function dontCreateMapWhiteSpace() { + const tree = { SECTION: [ { role: ROLE_TEXT_LEAF, name: "x" } ] }; + testAccessibleTree("container3", tree); + + getNode("c3_inner").style.textAlign = "center"; + document.body.offsetTop; // Flush layout. + window.windowUtils.advanceTimeAndRefresh(100); + + testAccessibleTree("container3", tree); + window.windowUtils.restoreNormalRefresh(); + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + dontCreateMapWhiteSpace(); + + gQueue = new eventQueue(); + + gQueue.push(new removeImg()); + gQueue.push(new insertImg()); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Make sure accessible tree is correct when rendered text is changed" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652"> + Mozilla Bug 625652 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Whitespace between the div and img tags will be inconsistent depending + on the image cache state and what optimizations layout was able to + apply. --> + <div id="container1"><img src="../moz.png"> <img id="img1" src="../moz.png"> <img src="../moz.png"></div> + <div><a id="container2"></a> <a><img src="../moz.png"></a></div> + + <div id="container3"> + <div id="c3_inner" role="presentation"> + x<map> </map> + </div> + </div> + + <div id="eventdump"></div> +</body> +</html> |