summaryrefslogtreecommitdiffstats
path: root/accessible/tests/mochitest/treeupdate
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--accessible/tests/mochitest/treeupdate/a11y.ini44
-rw-r--r--accessible/tests/mochitest/treeupdate/test_ariadialog.html113
-rw-r--r--accessible/tests/mochitest/treeupdate/test_ariahidden.html118
-rw-r--r--accessible/tests/mochitest/treeupdate/test_ariaowns.html797
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1040735.html40
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1100602.html103
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1175913.html95
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1189277.html81
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1276857.html131
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html33
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug852150.xhtml57
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug883708.xhtml31
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug884251.xhtml19
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug895082.html49
-rw-r--r--accessible/tests/mochitest/treeupdate/test_canvas.html87
-rw-r--r--accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml315
-rw-r--r--accessible/tests/mochitest/treeupdate/test_cssoverflow.html150
-rw-r--r--accessible/tests/mochitest/treeupdate/test_deck.xhtml154
-rw-r--r--accessible/tests/mochitest/treeupdate/test_delayed_removal.html453
-rw-r--r--accessible/tests/mochitest/treeupdate/test_doc.html415
-rw-r--r--accessible/tests/mochitest/treeupdate/test_gencontent.html187
-rw-r--r--accessible/tests/mochitest/treeupdate/test_general.html174
-rw-r--r--accessible/tests/mochitest/treeupdate/test_hidden.html125
-rw-r--r--accessible/tests/mochitest/treeupdate/test_imagemap.html399
-rw-r--r--accessible/tests/mochitest/treeupdate/test_list.html139
-rw-r--r--accessible/tests/mochitest/treeupdate/test_list_editabledoc.html100
-rw-r--r--accessible/tests/mochitest/treeupdate/test_listbox.xhtml181
-rw-r--r--accessible/tests/mochitest/treeupdate/test_menu.xhtml127
-rw-r--r--accessible/tests/mochitest/treeupdate/test_menubutton.xhtml140
-rw-r--r--accessible/tests/mochitest/treeupdate/test_optgroup.html126
-rw-r--r--accessible/tests/mochitest/treeupdate/test_recreation.html91
-rw-r--r--accessible/tests/mochitest/treeupdate/test_select.html136
-rw-r--r--accessible/tests/mochitest/treeupdate/test_shadow_slots.html467
-rw-r--r--accessible/tests/mochitest/treeupdate/test_shutdown.xhtml131
-rw-r--r--accessible/tests/mochitest/treeupdate/test_table.html74
-rw-r--r--accessible/tests/mochitest/treeupdate/test_textleaf.html167
-rw-r--r--accessible/tests/mochitest/treeupdate/test_tooltip.xhtml71
-rw-r--r--accessible/tests/mochitest/treeupdate/test_visibility.html411
-rw-r--r--accessible/tests/mochitest/treeupdate/test_whitespace.html200
39 files changed, 6731 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..b52eef3c84
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/a11y.ini
@@ -0,0 +1,44 @@
+[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_bug1100602.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_list.html]
+[test_list_editabledoc.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..d7e6fa58fb
--- /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: fixed; 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..200a302085
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -0,0 +1,797 @@
+<!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;
+
+ function doTest() {
+ gQueue = new eventQueue();
+
+ // 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(); // 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="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>
+</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_bug1100602.html b/accessible/tests/mochitest/treeupdate/test_bug1100602.html
new file mode 100644
index 0000000000..7233b585c1
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1100602.html
@@ -0,0 +1,103 @@
+<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">
+ /**
+ * Change list style type to none.
+ */
+ function hideBullet() {
+ this.eventSeq = [];
+ this.liAcc = getAccessible("list_element");
+ this.bullet = this.liAcc.firstChild;
+
+ this.eventSeq.push(new invokerChecker(EVENT_HIDE, this.bullet));
+ this.eventSeq.push(new invokerChecker(EVENT_REORDER, this.liAcc));
+
+ this.invoke = function hideBullet_invoke() {
+ getNode("list").setAttribute("style", "list-style-type: none;");
+ };
+
+ this.finalCheck = function hideBullet_finalCheck() {
+ is(this.liAcc.name, "list element",
+ "Check that first child of LI is not a bullet.");
+ };
+
+ this.getID = function hideBullet_getID() {
+ return "Hide bullet by setting style to none";
+ };
+ }
+
+ /**
+ * Change list style type to circles.
+ */
+ function showBullet() {
+ this.eventSeq = [];
+ this.liAcc = getAccessible("list_element");
+
+ this.eventSeq.push(new invokerChecker(EVENT_SHOW,
+ function(aNode) { return aNode.firstChild; },
+ this.liAcc));
+ this.eventSeq.push(new invokerChecker(EVENT_REORDER, this.liAcc));
+
+ this.invoke = function showBullet_invoke() {
+ getNode("list").setAttribute("style", "list-style-type: circle;");
+ };
+
+ this.finalCheck = function showBullet_finalCheck() {
+ is(this.liAcc.name, "â—¦ list element",
+ "Check that first child of LI is a circle bullet.");
+ };
+
+ this.getID = function showBullet_getID() {
+ return "Show bullet by setting style to circle";
+ };
+ }
+
+ var gQueue = null;
+ function doTest() {
+ var list = getNode("list");
+ list.setAttribute("style", "list-style-type: circle;");
+
+ gQueue = new eventQueue();
+ gQueue.push(new hideBullet());
+ gQueue.push(new showBullet());
+ gQueue.invoke(); // 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">
+ <li id="list_element">list element</li>
+ </ol>
+
+</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..6766d08a14
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1189277.html
@@ -0,0 +1,81 @@
+<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");
+ 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..3f91838e59
--- /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..59ab5cae76
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html
@@ -0,0 +1,150 @@
+<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 hidden 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("container3"));
+
+ 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 id="scrollarea2" style="overflow:hidden;"></div></div>
+ <div id="container3"><div style="overflow: hidden; 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..1a3d2c8877
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_delayed_removal.html
@@ -0,0 +1,453 @@
+<!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_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_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");
+ }
+
+ 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();
+ 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="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..b401b54eff
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_imagemap.html
@@ -0,0 +1,399 @@
+<!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",
+ "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",
+ "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",
+ "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_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_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..4684337b3d
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml
@@ -0,0 +1,140 @@
+<?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, aMenuItemRole)
+ {
+ var menuItemRole = aMenuItemRole || ROLE_MENUITEM;
+ this.button = getAccessible(aButtonID);
+ this.menupopup = this.button.firstChild;
+
+ var checker = new invokerChecker(EVENT_REORDER, this.menupopup);
+ this.__proto__ = new synthClick(aButtonID, checker);
+
+ this.invoke = function openMenu_invoke()
+ {
+ var tree =
+ { PUSHBUTTON: [
+ { MENUPOPUP: [ ] }
+ ] };
+ testAccessibleTree(this.button, tree);
+
+ this.__proto__.invoke();
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ var tree =
+ { PUSHBUTTON: [
+ { MENUPOPUP: [
+ { role: menuItemRole, children: [ ] },
+ { role: menuItemRole, children: [ ] }
+ ] }
+ ] };
+ testAccessibleTree(this.button, tree);
+
+ 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;
+ gQueue.push(new openMenu(columnPickerBtn, ROLE_CHECK_MENU_ITEM));
+ 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..288de1dd1d
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_optgroup.html
@@ -0,0 +1,126 @@
+<!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: [
+ { TEXT_LEAF: [] },
+ ] },
+ { COMBOBOX_OPTION: [
+ { TEXT_LEAF: [] },
+ ] },
+ ]},
+ { 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..92cf892569
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_recreation.html
@@ -0,0 +1,91 @@
+<!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 = "Set a listbox as multiselectable";
+ events = waitForOrderedEvents(
+ [[EVENT_HIDE, "div3"], [EVENT_SHOW, "div3"], [EVENT_REORDER, "container"]], msg);
+ document.getElementById("div3").setAttribute("aria-multiselectable", "true");
+ 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;
+
+ 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"/>
+ </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..c4c8c8b8f6
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_select.html
@@ -0,0 +1,136 @@
+<!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: [
+ { TEXT_LEAF: [] },
+ ] },
+ { COMBOBOX_OPTION: [
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] };
+ 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";
+ };
+ }
+
+ function doTest() {
+ const gQueue = new eventQueue();
+
+ gQueue.push(new addOptions("select"));
+ gQueue.push(new removeOptions("select"));
+ gQueue.push(new setRoleOnOption());
+
+ 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>
+</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..1a42fc4d52
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html
@@ -0,0 +1,467 @@
+<!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"]);
+ }
+
+ // 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"} ] }] });
+
+ }
+
+ async function doTest() {
+ await attachFlatShadow();
+
+ await attachOneDeepShadow();
+
+ await changeSlotFlat();
+
+ await changeSlotOneDeep();
+
+ await changeSlotNested();
+
+ 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();
+
+ 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="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="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..816cfec77c
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml
@@ -0,0 +1,71 @@
+<?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: [] }] });
+
+ 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>