diff options
Diffstat (limited to 'accessible/tests/mochitest/events')
57 files changed, 9818 insertions, 0 deletions
diff --git a/accessible/tests/mochitest/events/a11y.ini b/accessible/tests/mochitest/events/a11y.ini new file mode 100644 index 000000000..4ea7c9d10 --- /dev/null +++ b/accessible/tests/mochitest/events/a11y.ini @@ -0,0 +1,67 @@ +[DEFAULT] +support-files = + docload_wnd.html + focus.html + scroll.html + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + +[test_aria_alert.html] +[test_aria_menu.html] +[test_aria_objattr.html] +[test_aria_owns.html] +[test_aria_statechange.html] +[test_attrs.html] +[test_bug1322593.html] +[test_bug1322593-2.html] +[test_caretmove.html] +[test_caretmove.xul] +[test_coalescence.html] +[test_contextmenu.html] +[test_descrchange.html] +[test_docload.html] +[test_docload.xul] +skip-if = buildapp == 'mulet' +[test_docload_aria.html] +[test_dragndrop.html] +[test_flush.html] +[test_focus_aria_activedescendant.html] +[test_focus_autocomplete.xul] +# Disabled on Linux and Windows due to frequent failures - bug 695019, bug 890795 +skip-if = os == 'win' || os == 'linux' +[test_focus_browserui.xul] +[test_focus_canvas.html] +[test_focus_contextmenu.xul] +[test_focus_controls.html] +[test_focus_dialog.html] +[test_focus_doc.html] +[test_focus_general.html] +[test_focus_general.xul] +[test_focus_listcontrols.xul] +[test_focus_menu.xul] +[test_focus_name.html] +[test_focus_selects.html] +[test_focus_tabbox.xul] +[test_focus_tree.xul] +[test_fromUserInput.html] +[test_label.xul] +[test_menu.xul] +[test_mutation.html] +[test_mutation.xhtml] +[test_namechange.xul] +[test_namechange.html] +[test_scroll.xul] +[test_scroll_caret.xul] +[test_selection.html] +skip-if = buildapp == 'mulet' || os == 'mac' +[test_selection.xul] +skip-if = os == 'mac' +[test_selection_aria.html] +[test_statechange.html] +[test_text.html] +[test_text_alg.html] +[test_textattrchange.html] +[test_textselchange.html] +[test_tree.xul] +[test_valuechange.html] +skip-if = os == 'mac' diff --git a/accessible/tests/mochitest/events/docload_wnd.html b/accessible/tests/mochitest/events/docload_wnd.html new file mode 100644 index 000000000..86ddfac5e --- /dev/null +++ b/accessible/tests/mochitest/events/docload_wnd.html @@ -0,0 +1,39 @@ +<html> +<head> + <title>Accessible events testing for document</title> + <script> + const STATE_BUSY = Components.interfaces.nsIAccessibleStates.STATE_BUSY; + + var gService = null; + function waitForDocLoad() + { + if (!gService) { + gService = Components.classes["@mozilla.org/accessibilityService;1"]. + getService(Components.interfaces.nsIAccessibilityService); + } + + var accDoc = gService.getAccessibleFor(document); + + var state = {}; + accDoc.getState(state, {}); + if (state.value & STATE_BUSY) { + window.setTimeout(waitForDocLoad, 0); + return; + } + + hideIFrame(); + } + + function hideIFrame() + { + var iframe = document.getElementById("iframe"); + gService.getAccessibleFor(iframe.contentDocument); + iframe.style.display = 'none'; + } + </script> +</head> + +<body onload="waitForDocLoad();"> + <iframe id="iframe"></iframe> +</body> +</html> diff --git a/accessible/tests/mochitest/events/focus.html b/accessible/tests/mochitest/events/focus.html new file mode 100644 index 000000000..ab055df82 --- /dev/null +++ b/accessible/tests/mochitest/events/focus.html @@ -0,0 +1,10 @@ +<html> + +<head> + <title>editable document</title> +</head> + +<body contentEditable="true"> + editable document +</body> +</html> diff --git a/accessible/tests/mochitest/events/scroll.html b/accessible/tests/mochitest/events/scroll.html new file mode 100644 index 000000000..562e0a382 --- /dev/null +++ b/accessible/tests/mochitest/events/scroll.html @@ -0,0 +1,181 @@ +<html> + +<head> + <title>nsIAccessible actions testing for anchors</title> +</head> + +<body> + <p> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + </p> + <a name="link1">link1</a> + + <p style="color: blue"> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + </p> + + <h1 id="heading_1">heading 1</h1> + <p style="color: blue"> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + </p> +</body> +<html> diff --git a/accessible/tests/mochitest/events/test_aria_alert.html b/accessible/tests/mochitest/events/test_aria_alert.html new file mode 100644 index 000000000..2dab35723 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_alert.html @@ -0,0 +1,92 @@ +<html> + +<head> + <title>ARIA alert event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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"> + function showAlert(aID) + { + this.DOMNode = document.createElement("div"); + + this.invoke = function showAlert_invoke() + { + this.DOMNode.setAttribute("role", "alert"); + this.DOMNode.setAttribute("id", aID); + var text = document.createTextNode("alert"); + this.DOMNode.appendChild(text); + document.body.appendChild(this.DOMNode); + }; + + this.getID = function showAlert_getID() + { + return "Show ARIA alert " + aID; + }; + } + + function changeAlert(aID) + { + this.__defineGetter__("DOMNode", function() { return getNode(aID) }); + + this.invoke = function changeAlert_invoke() + { + this.DOMNode.textContent = "new alert"; + } + + this.getID = function showAlert_getID() + { + return "Change ARIA alert " + aID; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + //gA11yEventDumpToConsole = true; // debuging + //enableLogging("tree,events,verbose"); + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(nsIAccessibleEvent.EVENT_ALERT); + + gQueue.push(new showAlert("alert")); + gQueue.push(new changeAlert("alert")); + + 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=591199" + title="mochitest for bug 334386: fire alert event when ARIA alert is shown or new its children are inserted"> + Mozilla Bug 591199 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_aria_menu.html b/accessible/tests/mochitest/events/test_aria_menu.html new file mode 100644 index 000000000..5ac595ebf --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_menu.html @@ -0,0 +1,285 @@ +<html> + +<head> + <title>ARIA menu events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + 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="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + const kViaDisplayStyle = 0; + const kViaVisibilityStyle = 1; + + function focusMenu(aMenuBarID, aMenuID, aActiveMenuBarID) + { + this.eventSeq = []; + + if (aActiveMenuBarID) { + this.eventSeq.push(new invokerChecker(EVENT_MENU_END, + getNode(aActiveMenuBarID))); + } + + this.eventSeq.push(new invokerChecker(EVENT_MENU_START, getNode(aMenuBarID))); + this.eventSeq.push(new invokerChecker(EVENT_FOCUS, getNode(aMenuID))); + + this.invoke = function focusMenu_invoke() + { + getNode(aMenuID).focus(); + } + + this.getID = function focusMenu_getID() + { + return "focus menu '" + aMenuID + "'"; + } + } + + function showMenu(aMenuID, aParentMenuID, aHow) + { + this.menuNode = getNode(aMenuID); + + // Because of aria-owns processing we may have menupopup start fired before + // related show event. + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.menuNode), + new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)), + new invokerChecker(EVENT_MENUPOPUP_START, this.menuNode) + ]; + + this.invoke = function showMenu_invoke() + { + if (aHow == kViaDisplayStyle) + this.menuNode.style.display = "block"; + else + this.menuNode.style.visibility = "visible"; + }; + + this.getID = function showMenu_getID() + { + return "Show ARIA menu '" + aMenuID + "' by " + + (aHow == kViaDisplayStyle ? "display" : "visibility") + + " style tricks"; + }; + } + + function closeMenu(aMenuID, aParentMenuID, aHow) + { + this.menuNode = getNode(aMenuID); + this.menu = null; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getMenu, this), + new invokerChecker(EVENT_MENUPOPUP_END, getMenu, this), + new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)) + ]; + + this.invoke = function closeMenu_invoke() + { + // Store menu accessible reference while menu is still open. + this.menu = getAccessible(this.menuNode); + + // Hide menu. + if (aHow == kViaDisplayStyle) + this.menuNode.style.display = "none"; + else + this.menuNode.style.visibility = "hidden"; + } + + this.getID = function closeMenu_getID() + { + return "Close ARIA menu " + aMenuID + " by " + + (aHow == kViaDisplayStyle ? "display" : "visibility") + + " style tricks"; + } + + function getMenu(aThisObj) + { + return aThisObj.menu; + } + } + + function focusInsideMenu(aMenuID, aMenuBarID) + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getNode(aMenuID)) + ]; + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_MENU_END, getNode(aMenuBarID)) + ]; + + this.invoke = function focusInsideMenu_invoke() + { + getNode(aMenuID).focus(); + } + + this.getID = function focusInsideMenu_getID() + { + return "focus menu '" + aMenuID + "'"; + } + } + + function blurMenu(aMenuBarID) + { + var eventSeq = [ + new invokerChecker(EVENT_MENU_END, getNode(aMenuBarID)), + new invokerChecker(EVENT_FOCUS, getNode("outsidemenu")) + ]; + + this.__proto__ = new synthClick("outsidemenu", eventSeq); + + this.getID = function blurMenu_getID() + { + return "blur menu"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + //gA11yEventDumpToConsole = true; // debuging + //enableLogging("tree,events,verbose"); + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new focusMenu("menubar2", "menu-help")); + gQueue.push(new focusMenu("menubar", "menu-file", "menubar2")); + gQueue.push(new showMenu("menupopup-file", "menu-file", kViaDisplayStyle)); + gQueue.push(new closeMenu("menupopup-file", "menu-file", kViaDisplayStyle)); + gQueue.push(new showMenu("menupopup-edit", "menu-edit", kViaVisibilityStyle)); + gQueue.push(new closeMenu("menupopup-edit", "menu-edit", kViaVisibilityStyle)); + gQueue.push(new focusInsideMenu("menu-edit", "menubar")); + gQueue.push(new blurMenu("menubar")); + + gQueue.push(new focusMenu("menubar3", "mb3-mi-outside")); + gQueue.push(new showMenu("mb4-menu", document, kViaDisplayStyle)); + gQueue.push(new focusMenu("menubar4", "mb4-item1")); + gQueue.push(new focusMenu("menubar5", "mb5-mi")); + + gQueue.push(new synthFocus("mi6")); + + 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=606207" + title="Dojo dropdown buttons are broken"> + Bug 606207 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=614829" + title="Menupopup end event isn't fired for ARIA menus"> + Bug 614829 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=615189" + title="Clean up FireAccessibleFocusEvent"> + Bug 615189 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Bug 673958 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=933322" + title="menustart/end events are missing when aria-owns makes a menu hierarchy"> + Bug 933322 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=934460" + title="menustart/end events may be missed when top level menuitem is focused"> + Bug 934460 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=970005" + title="infinite long loop in a11y:FocusManager::ProcessFocusEvent"> + Bug 970005 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="menubar" role="menubar"> + <div id="menu-file" role="menuitem" tabindex="0"> + File + <div id="menupopup-file" role="menu" style="display: none;"> + <div id="menuitem-newtab" role="menuitem" tabindex="0">New Tab</div> + <div id="menuitem-newwindow" role="menuitem" tabindex="0">New Window</div> + </div> + </div> + <div id="menu-edit" role="menuitem" tabindex="0"> + Edit + <div id="menupopup-edit" role="menu" style="visibility: hidden;"> + <div id="menuitem-undo" role="menuitem" tabindex="0">Undo</div> + <div id="menuitem-redo" role="menuitem" tabindex="0">Redo</div> + </div> + </div> + </div> + <div id="menubar2" role="menubar"> + <div id="menu-help" role="menuitem" tabindex="0"> + Help + <div id="menupopup-help" role="menu" style="display: none;"> + <div id="menuitem-about" role="menuitem" tabindex="0">About</div> + </div> + </div> + </div> + <div tabindex="0" id="outsidemenu">outsidemenu</div> + + <!-- aria-owns relations --> + <div id="menubar3" role="menubar" aria-owns="mb3-mi-outside"></div> + <div id="mb3-mi-outside" role="menuitem" tabindex="0">Outside</div> + + <div id="menubar4" role="menubar"> + <div id="mb4_topitem" role="menuitem" aria-haspopup="true" + aria-owns="mb4-menu">Item</div> + </div> + <div id="mb4-menu" role="menu" style="display:none;"> + <div role="menuitem" id="mb4-item1" tabindex="0">Item 1.1</div> + <div role="menuitem" tabindex="0">Item 1.2</div> + </div> + + <!-- focus top-level menu item having haspopup --> + <div id="menubar5" role="menubar"> + <div role="menuitem" aria-haspopup="true" id="mb5-mi" tabindex="0"> + Item + <div role="menu" style="display:none;"> + <div role="menuitem" tabindex="0">Item 1.1</div> + <div role="menuitem" tabindex="0">Item 1.2</div> + </div> + </div> + </div> + + <!-- other aria-owns relations --> + <div id="mi6" tabindex="0" role="menuitem">aria-owned item</div> + <div aria-owns="mi6">Obla</div> + + <div id="eventdump"></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_aria_objattr.html b/accessible/tests/mochitest/events/test_aria_objattr.html new file mode 100644 index 000000000..5f16ba794 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_objattr.html @@ -0,0 +1,118 @@ +<html> + +<head> + <title>Accessible ARIA object attribute changes</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + /** + * Do tests. + */ + var gQueue = null; + function updateAttribute(aID, aAttr, aValue) + { + this.node = getNode(aID); + this.accessible = getAccessible(this.node); + + this.eventSeq = [ + new objAttrChangedChecker(aID, aAttr), + ]; + + this.invoke = function updateAttribute_invoke() + { + this.node.setAttribute(aAttr, aValue); + }; + + this.getID = function updateAttribute_getID() + { + return aAttr + " for " + aID + " " + aValue; + }; + } + + function updateARIAHidden(aID, aIsDefined, aChildId) + { + this.__proto__ = new updateAttribute(aID, "aria-hidden", + aIsDefined ? "true" : "false"); + + this.finalCheck = function updateARIAHidden() + { + if (aIsDefined) { + testAttrs(aID, {"hidden" : "true"}, true); + testAttrs(aChildId, {"hidden" : "true"}, true); + } else { + testAbsentAttrs(aID, { "hidden": "true"}); + testAbsentAttrs(aChildId, { "hidden": "true"}); + } + } + } + + // Debug stuff. + // gA11yEventDumpID = "eventdump"; + //gA11yEventDumpToConsole = true; + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new updateARIAHidden("hideable", true, "hideable_child")); + gQueue.push(new updateARIAHidden("hideable", false, "hideable_child")); + + gQueue.push(new updateAttribute("sortable", "aria-sort", "ascending")); + + // For experimental ARIA extensions + gQueue.push(new updateAttribute("custom", "aria-blah", "true")); + + 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=581096" + title="Add support for aria-hidden"> + Mozilla Bug 581096 + </a> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=640707" + title="Add event support for aria-sort"> + Mozilla Bug 640707 + </a> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=640707" + title="Expand support for aria attribute change events"> + Mozilla Bug 563862 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div id="hideable"><div id="hideable_child">Hi</div><div>there</div></div> + + <div id="sortable" role="columnheader" aria-sort="none">aria-sort</div> + + <div id="custom" role="custom" aria-blah="false">Fat free cheese</div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_aria_owns.html b/accessible/tests/mochitest/events/test_aria_owns.html new file mode 100644 index 000000000..a415a6d8d --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_owns.html @@ -0,0 +1,129 @@ +<html> + +<head> + <title>Aria-owns targets shouldn't be on invalidation list so shouldn't have + show/hide events</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + //////////////////////////////////////////////////////////////////////////// + // Do tests. + + //gA11yEventDumpToConsole = true; // debug stuff + //enableLogging("tree,eventTree,verbose"); + + /** + * Aria-owns target shouldn't have a show event. + * Markup: + * <div id="t1_fc" aria-owns="t1_owns"></div> + * <span id="t1_owns"></div> + */ + function testAriaOwns() + { + this.parent = getNode("t1"); + this.fc = document.createElement("div"); + this.fc.setAttribute("id", "t1_fc"); + this.owns = document.createElement("span"); + this.owns.setAttribute("id", "t1_owns"); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.fc), + new unexpectedInvokerChecker(EVENT_SHOW, this.owns) + ]; + + this.invoke = function testAriaOwns_invoke() + { + getNode("t1").appendChild(this.fc); + getNode("t1").appendChild(this.owns); + getNode("t1_fc").setAttribute("aria-owns", "t1_owns"); + }; + + this.getID = function testAriaOwns_getID() { + return "Aria-owns target shouldn't have show event"; + }; + } + + /** + * Target of both aria-owns and other aria attribute like aria-labelledby + * shouldn't have a show event. + * Markup: + * <div id="t2_fc" aria-owns="t1_owns"></div> + * <div id="t2_sc" aria-labelledby="t2_owns"></div> + * <span id="t2_owns"></div> + */ + function testAriaOwnsAndLabelledBy() + { + this.parent = getNode("t2"); + this.fc = document.createElement("div"); + this.fc.setAttribute("id", "t2_fc"); + this.sc = document.createElement("div"); + this.sc.setAttribute("id", "t2_sc"); + this.owns = document.createElement("span"); + this.owns.setAttribute("id", "t2_owns"); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.fc), + new invokerChecker(EVENT_SHOW, this.sc), + new unexpectedInvokerChecker(EVENT_SHOW, this.owns) + ]; + + this.invoke = function testAriaOwns_invoke() + { + getNode("t2").appendChild(this.fc); + getNode("t2").appendChild(this.sc); + getNode("t2").appendChild(this.owns); + getNode("t2_fc").setAttribute("aria-owns", "t2_owns"); + getNode("t2_sc").setAttribute("aria-labelledby", "t2_owns"); + }; + + this.getID = function testAriaOwns_getID() { + return "Aria-owns and aria-labelledby target shouldn't have show event"; + }; + } + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + gQueue.push(new testAriaOwns()); + gQueue.push(new testAriaOwnsAndLabelledBy()); + + 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=1296420" + title="Aria-owns targets shouldn't be on invalidation list so shouldn't + have show/hide events"> + Mozilla Bug 1296420 + </a><br> + + <div id="testContainer"> + <div id="t1"></div> + + <div id="t2"></div> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_aria_statechange.html b/accessible/tests/mochitest/events/test_aria_statechange.html new file mode 100644 index 000000000..d8c833157 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_statechange.html @@ -0,0 +1,208 @@ +<html> + +<head> + <title>ARIA state change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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"> + + + /** + * Do tests. + */ + var gQueue = null; + + //gA11yEventDumpID = "eventdump"; // debugging + //gA11yEventDumpToConsole = true; // debugging + + function expandNode(aID, aIsExpanded) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new expandedStateChecker(aIsExpanded, this.DOMNode) + ]; + + this.invoke = function expandNode_invoke() + { + this.DOMNode.setAttribute("aria-expanded", + (aIsExpanded ? "true" : "false")); + }; + + this.getID = function expandNode_getID() + { + return prettyName(aID) + " aria-expanded changed to '" + aIsExpanded + "'"; + }; + } + + function busyify(aID, aIsBusy) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new stateChangeChecker(STATE_BUSY, kOrdinalState, aIsBusy, this.DOMNode) + ]; + + this.invoke = function busyify_invoke() + { + this.DOMNode.setAttribute("aria-busy", (aIsBusy ? "true" : "false")); + }; + + this.getID = function busyify_getID() + { + return prettyName(aID) + " aria-busy changed to '" + aIsBusy + "'"; + }; + } + + function setAttrOfMixedType(aID, aAttr, aState, aValue) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new stateChangeChecker(aState, kOrdinalState, + aValue == "true", this.DOMNode) + ]; + + if (hasState(aID, STATE_MIXED) || aValue == "mixed") { + this.eventSeq.push( + new stateChangeChecker(STATE_MIXED, kOrdinalState, + aValue == "mixed", this.DOMNode) + ); + } + + this.invoke = function setAttrOfMixedType_invoke() + { + this.DOMNode.setAttribute(aAttr, aValue); + }; + + this.getID = function setAttrOfMixedType_getID() + { + return prettyName(aID) + " " + aAttr + " changed to '" + aValue + "'"; + }; + } + + function setPressed(aID, aValue) + { + this.__proto__ = + new setAttrOfMixedType(aID, "aria-pressed", STATE_PRESSED, aValue); + } + + function setChecked(aID, aValue) + { + this.__proto__ = + new setAttrOfMixedType(aID, "aria-checked", STATE_CHECKED, aValue); + } + + function buildQueueForAttr(aList, aQueue, aID, aInvokerFunc) + { + for (var i = 0; i < aList.length; i++) { + for (var j = i + 1; j < aList.length; j++) { + // XXX: changes from/to "undefined"/"" shouldn't fire state change + // events, bug 472142. + aQueue.push(new aInvokerFunc(aID, aList[i])); + aQueue.push(new aInvokerFunc(aID, aList[j])); + } + } + } + + function buildQueueForAttrOfMixedType(aQueue, aID, aInvokerFunc) + { + var list = [ "", "undefined", "false", "true", "mixed" ]; + buildQueueForAttr(list, aQueue, aID, aInvokerFunc); + } + + function buildQueueForAttrOfBoolType(aQueue, aID, aInvokerFunc) + { + var list = [ "", "undefined", "false", "true" ]; + buildQueueForAttr(list, aQueue, aID, aInvokerFunc); + } + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new expandNode("section", true)); + gQueue.push(new expandNode("section", false)); + gQueue.push(new expandNode("div", true)); + gQueue.push(new expandNode("div", false)); + + gQueue.push(new busyify("aria_doc", true)); + gQueue.push(new busyify("aria_doc", false)); + + buildQueueForAttrOfMixedType(gQueue, "pressable", setPressed); + buildQueueForAttrOfMixedType(gQueue, "pressable_native", setPressed); + buildQueueForAttrOfMixedType(gQueue, "checkable", setChecked); + buildQueueForAttrOfBoolType(gQueue, "checkableBool", setChecked); + + 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=551684" + title="No statechange event for aria-expanded on native HTML elements, is fired on ARIA widgets"> + Mozilla Bug 551684 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=648133" + title="fire state change event for aria-busy"> + Mozilla Bug 648133 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=467143" + title="mixed state change event is fired for focused accessible only"> + Mozilla Bug 467143 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958" + title="Pressed state is not exposed on a button element with aria-pressed attribute"> + Mozilla Bug 989958 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563" + title="Support ARIA 1.1 switch role"> + Mozilla Bug 1136563 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <!-- aria-expanded --> + <div id="section" role="section" aria-expanded="false">expandable section</div> + <div id="div" aria-expanded="false">expandable native div</div> + + <!-- aria-busy --> + <div id="aria_doc" role="document" tabindex="0">A document</div> + + <!-- aria-pressed --> + <div id="pressable" role="button"></div> + <button id="pressable_native"></button> + + <!-- aria-checked --> + <div id="checkable" role="checkbox"></div> + <div id="checkableBool" role="switch"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_attrs.html b/accessible/tests/mochitest/events/test_attrs.html new file mode 100644 index 000000000..1d5577572 --- /dev/null +++ b/accessible/tests/mochitest/events/test_attrs.html @@ -0,0 +1,90 @@ +<html> + +<head> + <title>Event object attributes tests</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + + /** + * Test "event-from-input" object attribute. + */ + function eventFromInputChecker(aEventType, aID, aValue, aNoTargetID) + { + this.type = aEventType; + this.target = getAccessible(aID); + + this.noTarget = getNode(aNoTargetID); + + this.check = function checker_check(aEvent) + { + testAttrs(aEvent.accessible, { "event-from-input": aValue }, true); + + var accessible = getAccessible(this.noTarget); + testAbsentAttrs(accessible, { "event-from-input": "" }); + } + } + + /** + * Do tests. + */ + var gQueue = null; + + // gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + function doTests() + { + gQueue = new eventQueue(); + + var id = "textbox", noTargetId = "textarea"; + var checker = + new eventFromInputChecker(EVENT_FOCUS, id, "false", noTargetId); + gQueue.push(new synthFocus(id, checker)); + + if (!MAC) { // Mac failure is bug 541093 + var checker = + new eventFromInputChecker(EVENT_TEXT_CARET_MOVED, id, "true", noTargetId); + gQueue.push(new synthHomeKey(id, checker)); + } + + 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=540285" + title="Event object attributes testing"> + Mozilla Bug 540285 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="textbox" value="hello"> + <textarea id="textarea"></textarea> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_bug1322593-2.html b/accessible/tests/mochitest/events/test_bug1322593-2.html new file mode 100644 index 000000000..20136d393 --- /dev/null +++ b/accessible/tests/mochitest/events/test_bug1322593-2.html @@ -0,0 +1,83 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function changeMultipleElements() + { + this.node1 = getNode("span1"); + this.node2 = getNode("span2"); + + this.eventSeq = [ + new textChangeChecker("container", 0, 5, "hello", false, undefined, true), + new textChangeChecker("container", 6, 11, "world", false, undefined, true), + new orderChecker(), + new textChangeChecker("container", 0, 1, "a", true, undefined, true), + new textChangeChecker("container", 7, 8, "b", true, undefined, true) + ]; + + this.invoke = function changeMultipleElements_invoke() + { + this.node1.textContent = "a"; + this.node2.textContent = "b"; + } + + this.getID = function changeMultipleElements_invoke_getID() + { + return "Change the text content of multiple sibling divs"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests +// gA11yEventDumpToConsole = true; // debugging + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new changeMultipleElements()); + + 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=1322593" + title="missing text change events when multiple elements updated at once"> + Mozilla Bug 1322593 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="container"> + <span id="span1">hello</span> + <span>your</span> + <span id="span2">world</span> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_bug1322593.html b/accessible/tests/mochitest/events/test_bug1322593.html new file mode 100644 index 000000000..38786d0b9 --- /dev/null +++ b/accessible/tests/mochitest/events/test_bug1322593.html @@ -0,0 +1,80 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function changeMultipleElements() + { + this.node1 = getNode("div1"); + this.node2 = getNode("div2"); + + this.eventSeq = [ + new textChangeChecker("div1", 0, 5, "hello", false, undefined, true), + new textChangeChecker("div2", 0, 5, "world", false, undefined, true), + new orderChecker(), + new textChangeChecker("div1", 0, 1, "a", true, undefined, true), + new textChangeChecker("div2", 0, 1, "b", true, undefined, true) + ]; + + this.invoke = function changeMultipleElements_invoke() + { + this.node1.textContent = "a"; + this.node2.textContent = "b"; + } + + this.getID = function changeMultipleElements_invoke_getID() + { + return "Change the text content of multiple sibling divs"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests +// gA11yEventDumpToConsole = true; // debugging + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new changeMultipleElements()); + + 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=1322593" + title="missing text change events when multiple elements updated at once"> + Mozilla Bug 1322593 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="div1">hello</div> + <div id="div2">world</div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_caretmove.html b/accessible/tests/mochitest/events/test_caretmove.html new file mode 100644 index 000000000..6d39c4ef6 --- /dev/null +++ b/accessible/tests/mochitest/events/test_caretmove.html @@ -0,0 +1,140 @@ +<html> + +<head> + <title>Accessible caret move events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + /** + * Click checker. + */ + function clickChecker(aCaretOffset, aID, aExtraNodeOrID, aExtraCaretOffset) + { + this.__proto__ = new caretMoveChecker(aCaretOffset, aID); + + this.extraNode = getNode(aExtraNodeOrID); + + this.check = function clickChecker_check(aEvent) + { + this.__proto__.check(aEvent); + + if (this.extraNode) { + var acc = getAccessible(this.extraNode, [nsIAccessibleText]); + is(acc.caretOffset, aExtraCaretOffset, + "Wrong caret offset for " + aExtraNodeOrID); + } + } + } + + /** + * Do tests. + */ + var gQueue = null; + + // gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + function doTests() + { + // test caret move events and caret offsets + gQueue = new eventQueue(); + + var id = "textbox"; + gQueue.push(new synthFocus(id, new caretMoveChecker(5, id))); + gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, id))); + gQueue.push(new synthClick(id, new caretMoveChecker(0, id))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id))); + + id = "textarea"; + gQueue.push(new synthClick(id, new caretMoveChecker(0, id))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id))); + gQueue.push(new synthDownKey(id, new caretMoveChecker(12, id))); + + id = "textarea_wrapped"; + gQueue.push(new setCaretOffset(id, 4, id)); + gQueue.push(new synthLeftKey(id, new caretMoveChecker(4, id))); + + id = "p"; + gQueue.push(new synthClick(id, new caretMoveChecker(0, id))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id))); + gQueue.push(new synthDownKey(id, new caretMoveChecker(6, id))); + + id = "p1_in_div"; + gQueue.push(new synthClick(id, new clickChecker(0, id, "p2_in_div", -1))); + + id = "p"; + gQueue.push(new synthShiftTab(id, new caretMoveChecker(0, id))); + id = "textarea"; + gQueue.push(new synthShiftTab(id, new caretMoveChecker(12, id))); + id = "p"; + gQueue.push(new synthTab(id, new caretMoveChecker(0, id))); + + // Set caret after a child of span element, i.e. after 'text' text. + gQueue.push(new moveCaretToDOMPoint("test1", getNode("test1_span"), 1, + 4, "test1")); + gQueue.push(new moveCaretToDOMPoint("test2", getNode("test2_span"), 1, + 4, "test2")); + + // empty text element + gQueue.push(new moveCaretToDOMPoint("test3", getNode("test3"), 0, + 0, "test3")); + gQueue.push(new moveCaretToDOMPoint("test4", getNode("test4_span"), 0, + 0, "test4")); + + 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=454377" + title="Accessible caret move events testing"> + Bug 454377 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=567571" + title="caret-moved events missing at the end of a wrapped line of text"> + Bug 567571 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=824901" + title="HyperTextAccessible::DOMPointToHypertextOffset fails for node and offset equal to node child count"> + Bug 824901 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="textbox" value="hello"/> + <textarea id="textarea">text<br>text</textarea> + <p id="p" contentEditable="true"><span>text</span><br/>text</p> + <div id="div" contentEditable="true"><p id="p1_in_div">text</p><p id="p2_in_div">text</p></div> + + <p contentEditable="true" id="test1"><span id="test1_span">text</span>ohoho</p> + <p contentEditable="true" id="test2"><span><span id="test2_span">text</span></span>ohoho</p> + <p contentEditable="true" id="test3"></p> + <p contentEditable="true" id="test4"><span id="test4_span"></span></p> + + <textarea id="textarea_wrapped" cols="5">hey friend</textarea> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_caretmove.xul b/accessible/tests/mochitest/events/test_caretmove.xul new file mode 100644 index 000000000..cf4dcd483 --- /dev/null +++ b/accessible/tests/mochitest/events/test_caretmove.xul @@ -0,0 +1,72 @@ +<?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="Caret move event testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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"> + /** + * Do tests. + */ + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTests() + { + if (MAC) { + todo(false, "Make these tests pass on OSX (bug 650294)"); + SimpleTest.finish(); + return; + } + + gQueue = new eventQueue(EVENT_TEXT_CARET_MOVED); + + var id = "textbox"; + var input = getNode(id).inputField; + gQueue.push(new synthFocus(id, new caretMoveChecker(5, input))); + gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, input))); + gQueue.push(new synthHomeKey(id, new caretMoveChecker(0, input))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, input))); + + 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=634240" + title="No caret move events are fired for XUL textbox accessible"> + Mozilla Bug 634240 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <textbox id="textbox" value="hello"/> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_coalescence.html b/accessible/tests/mochitest/events/test_coalescence.html new file mode 100644 index 000000000..d95ef99b0 --- /dev/null +++ b/accessible/tests/mochitest/events/test_coalescence.html @@ -0,0 +1,864 @@ +<html> + +<head> + <title>Accessible mutation events coalescence testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + //////////////////////////////////////////////////////////////////////////// + // Invoker base classes + + const kRemoveElm = 1; + const kHideElm = 2; + const kAddElm = 3; + const kShowElm = 4; + + /** + * Base class to test of mutation events coalescence. + */ + function coalescenceBase(aChildAction, aParentAction, + aPerformActionOnChildInTheFirstPlace) + { + // Invoker interface + + this.invoke = function coalescenceBase_invoke() + { + if (aPerformActionOnChildInTheFirstPlace) { + this.invokeAction(this.childNode, aChildAction); + this.invokeAction(this.parentNode, aParentAction); + } else { + this.invokeAction(this.parentNode, aParentAction); + this.invokeAction(this.childNode, aChildAction); + } + } + + this.getID = function coalescenceBase_getID() + { + var childAction = this.getActionName(aChildAction) + " child"; + var parentAction = this.getActionName(aParentAction) + " parent"; + + if (aPerformActionOnChildInTheFirstPlace) + return childAction + " and then " + parentAction; + + return parentAction + " and then " + childAction; + } + + this.finalCheck = function coalescenceBase_check() + { + if (this.getEventType(aChildAction) == EVENT_HIDE) { + testIsDefunct(this.child); + } + if (this.getEventType(aParentAction) == EVENT_HIDE) { + testIsDefunct(this.parent); + } + } + + // Implementation details + + this.invokeAction = function coalescenceBase_invokeAction(aNode, aAction) + { + switch (aAction) { + case kRemoveElm: + aNode.parentNode.removeChild(aNode); + break; + + case kHideElm: + aNode.style.display = "none"; + break; + + case kAddElm: + if (aNode == this.parentNode) + this.hostNode.appendChild(this.parentNode); + else + this.parentNode.appendChild(this.childNode); + break; + + case kShowElm: + aNode.style.display = "block"; + break; + + default: + return INVOKER_ACTION_FAILED; + } + } + + this.getEventType = function coalescenceBase_getEventType(aAction) + { + switch (aAction) { + case kRemoveElm: case kHideElm: + return EVENT_HIDE; + case kAddElm: case kShowElm: + return EVENT_SHOW; + } + } + + this.getActionName = function coalescenceBase_getActionName(aAction) + { + switch (aAction) { + case kRemoveElm: + return "remove"; + case kHideElm: + return "hide"; + case kAddElm: + return "add"; + case kShowElm: + return "show"; + default: + return "??"; + } + } + + this.initSequence = function coalescenceBase_initSequence() + { + // expected events + var eventType = this.getEventType(aParentAction); + this.eventSeq = [ + new invokerChecker(eventType, this.parentNode), + new invokerChecker(EVENT_REORDER, this.hostNode) + ]; + + // unexpected events + this.unexpectedEventSeq = [ + new invokerChecker(this.getEventType(aChildAction), this.childNode), + new invokerChecker(EVENT_REORDER, this.parentNode) + ]; + } + } + + /** + * Remove or hide mutation events coalescence testing. + */ + function removeOrHideCoalescenceBase(aChildID, aParentID, + aChildAction, aParentAction, + aPerformActionOnChildInTheFirstPlace) + { + this.__proto__ = new coalescenceBase(aChildAction, aParentAction, + aPerformActionOnChildInTheFirstPlace); + + this.init = function removeOrHideCoalescenceBase_init() + { + this.childNode = getNode(aChildID); + this.parentNode = getNode(aParentID); + this.child = getAccessible(this.childNode); + this.parent = getAccessible(this.parentNode); + this.hostNode = this.parentNode.parentNode; + } + + // Initalization + + this.init(); + this.initSequence(); + } + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + /** + * Remove child node and then its parent node from DOM tree. + */ + function removeChildNParent(aChildID, aParentID) + { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kRemoveElm, kRemoveElm, + true); + } + + /** + * Remove parent node and then its child node from DOM tree. + */ + function removeParentNChild(aChildID, aParentID) + { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kRemoveElm, kRemoveElm, + false); + } + + /** + * Hide child node and then its parent node. + */ + function hideChildNParent(aChildID, aParentID) + { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kHideElm, kHideElm, + true); + } + + /** + * Hide parent node and then its child node. + */ + function hideParentNChild(aChildID, aParentID) + { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kHideElm, kHideElm, + false); + } + + /** + * Hide child node and then remove its parent node. + */ + function hideChildNRemoveParent(aChildID, aParentID) + { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kHideElm, kRemoveElm, + true); + } + + /** + * Hide parent node and then remove its child node. + */ + function hideParentNRemoveChild(aChildID, aParentID) + { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kRemoveElm, kHideElm, + false); + } + + /** + * Remove child node and then hide its parent node. + */ + function removeChildNHideParent(aChildID, aParentID) + { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kRemoveElm, kHideElm, + true); + } + + /** + * Remove parent node and then hide its child node. + */ + function removeParentNHideChild(aChildID, aParentID) + { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kHideElm, kRemoveElm, + false); + } + + /** + * Create and append parent node and create and append child node to it. + */ + function addParentNChild(aHostID, aPerformActionOnChildInTheFirstPlace) + { + this.init = function addParentNChild_init() + { + this.hostNode = getNode(aHostID); + this.parentNode = document.createElement("select"); + this.childNode = document.createElement("option"); + this.childNode.textContent = "testing"; + } + + this.__proto__ = new coalescenceBase(kAddElm, kAddElm, + aPerformActionOnChildInTheFirstPlace); + + this.init(); + this.initSequence(); + } + + /** + * Show parent node and show child node to it. + */ + function showParentNChild(aParentID, aChildID, + aPerformActionOnChildInTheFirstPlace) + { + this.init = function showParentNChild_init() + { + this.parentNode = getNode(aParentID); + this.hostNode = this.parentNode.parentNode; + this.childNode = getNode(aChildID); + } + + this.__proto__ = new coalescenceBase(kShowElm, kShowElm, + aPerformActionOnChildInTheFirstPlace); + + this.init(); + this.initSequence(); + } + + /** + * Create and append child node to the DOM and then show parent node. + */ + function showParentNAddChild(aParentID, + aPerformActionOnChildInTheFirstPlace) + { + this.init = function showParentNAddChild_init() + { + this.parentNode = getNode(aParentID); + this.hostNode = this.parentNode.parentNode; + this.childNode = document.createElement("option"); + this.childNode.textContent = "testing"; + } + + this.__proto__ = new coalescenceBase(kAddElm, kShowElm, + aPerformActionOnChildInTheFirstPlace); + + this.init(); + this.initSequence(); + } + + /** + * Remove children and parent + */ + function removeGrandChildrenNHideParent(aChild1Id, aChild2Id, aParentId) + { + this.child1 = getNode(aChild1Id); + this.child2 = getNode(aChild2Id); + this.parent = getNode(aParentId); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getAccessible(aParentId)), + new invokerChecker(EVENT_REORDER, getNode(aParentId).parentNode), + new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild1Id)), + new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild2Id)), + new unexpectedInvokerChecker(EVENT_REORDER, getAccessible(aParentId)) + ]; + + this.invoke = function removeGrandChildrenNHideParent_invoke() + { + this.child1.parentNode.removeChild(this.child1); + this.child2.parentNode.removeChild(this.child2); + this.parent.hidden = true; + } + + this.getID = function removeGrandChildrenNHideParent_getID() { + return "remove grand children of different parents and then hide their grand parent"; + } + } + + /** + * Remove a child, and then its parent. + */ + function test3() + { + this.o = getAccessible("t3_o"); + this.ofc = getAccessible("t3_o").firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.o), + new invokerChecker(EVENT_REORDER, "t3_lb"), + new unexpectedInvokerChecker(EVENT_HIDE, this.ofc), + new unexpectedInvokerChecker(EVENT_REORDER, this.o) + ]; + + this.invoke = function test3_invoke() + { + getNode("t3_o").textContent = ""; + getNode("t3_lb").removeChild(getNode("t3_o")); + } + + this.finalCheck = function test3_finalCheck() + { + testIsDefunct(this.o); + testIsDefunct(this.ofc); + } + + this.getID = function test3_getID() { + return "remove a child, and then its parent"; + } + } + + /** + * Remove children, and then a parent of 2nd child. + */ + function test4() + { + this.o1 = getAccessible("t4_o1"); + this.o1fc = this.o1.firstChild; + this.o2 = getAccessible("t4_o2"); + this.o2fc = this.o2.firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.o1fc), + new invokerChecker(EVENT_HIDE, this.o2), + new invokerChecker(EVENT_REORDER, "t4_lb"), + new unexpectedInvokerChecker(EVENT_HIDE, this.o2fc), + new unexpectedInvokerChecker(EVENT_REORDER, this.o1), + new unexpectedInvokerChecker(EVENT_REORDER, this.o2) + ]; + + this.invoke = function test4_invoke() + { + getNode("t4_o1").textContent = ""; + getNode("t4_o2").textContent = ""; + getNode("t4_lb").removeChild(getNode("t4_o2")); + } + + this.finalCheck = function test4_finalCheck() + { + testIsDefunct(this.o1fc); + testIsDefunct(this.o2); + testIsDefunct(this.o2fc); + } + + this.getID = function test4_getID() { + return "remove children, and then a parent of 2nd child"; + } + } + + /** + * Remove a child, remove a parent sibling, remove the parent + */ + function test5() + { + this.o = getAccessible("t5_o"); + this.ofc = this.o.firstChild; + this.b = getAccessible("t5_b"); + this.lb = getAccessible("t5_lb"); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.b), + new invokerChecker(EVENT_HIDE, this.o), + new invokerChecker(EVENT_REORDER, "t5"), + new unexpectedInvokerChecker(EVENT_HIDE, this.ofc), + new unexpectedInvokerChecker(EVENT_REORDER, this.o), + new unexpectedInvokerChecker(EVENT_REORDER, this.lb) + ]; + + this.invoke = function test5_invoke() + { + getNode("t5_o").textContent = ""; + getNode("t5").removeChild(getNode("t5_b")); + getNode("t5_lb").removeChild(getNode("t5_o")); + } + + this.finalCheck = function test5_finalCheck() + { + testIsDefunct(this.ofc); + testIsDefunct(this.o); + testIsDefunct(this.b); + } + + this.getID = function test5_getID() { + return "remove a child, remove a parent sibling, remove the parent"; + } + } + + /** + * Insert accessibles with a child node moved by aria-owns + * Markup: + * <div id="t6_fc"> + * <div id="t6_owns"></div> + * </div> + * <div id="t6_sc" aria-owns="t6_owns"></div> + */ + function test6() + { + this.parent = getNode("t6"); + this.fc = document.createElement("div"); + this.fc.setAttribute("id", "t6_fc"); + this.owns = document.createElement("div"); + this.owns.setAttribute("id", "t6_owns"); + this.sc = document.createElement("div"); + this.sc.setAttribute("id", "t6_sc"); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.fc), + new invokerChecker(EVENT_SHOW, this.sc), + new invokerChecker(EVENT_REORDER, this.parent), + new unexpectedInvokerChecker(EVENT_REORDER, this.fc), + new unexpectedInvokerChecker(EVENT_REORDER, this.sc), + new unexpectedInvokerChecker(EVENT_HIDE, this.owns), + new unexpectedInvokerChecker(EVENT_SHOW, this.owns) + ]; + + this.invoke = function test6_invoke() + { + getNode("t6").appendChild(this.fc); + getNode("t6_fc").appendChild(this.owns); + getNode("t6").appendChild(this.sc); + getNode("t6_sc").setAttribute("aria-owns", "t6_owns"); + }; + + this.getID = function test6_getID() { + return "Insert accessibles with a child node moved by aria-owns"; + }; + } + + /** + * Insert text nodes under direct and grand children, and then hide + * their container by means of aria-owns. + * + * Markup: + * <div id="t7_moveplace" aria-owns="t7_c"></div> + * <div id="t7_c"> + * <div id="t7_c_directchild">ha</div> + * <div><div id="t7_c_grandchild">ha</div></div> + * </div> + */ + function test7() + { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode('t7_c')), + new invokerChecker(EVENT_SHOW, getNode('t7_c')), + new invokerChecker(EVENT_REORDER, getNode('t7')), + new unexpectedInvokerChecker(EVENT_REORDER, getNode('t7_c_directchild')), + new unexpectedInvokerChecker(EVENT_REORDER, getNode('t7_c_grandchild')), + new unexpectedInvokerChecker(EVENT_SHOW, () => getNode('t7_c_directchild').firstChild), + new unexpectedInvokerChecker(EVENT_SHOW, () => getNode('t7_c_grandchild').firstChild) + ]; + + this.invoke = function test7_invoke() + { + getNode('t7_c_directchild').textContent = 'ha'; + getNode('t7_c_grandchild').textContent = 'ha'; + getNode('t7_moveplace').setAttribute('aria-owns', 't7_c'); + }; + + this.getID = function test7_getID() { + return "Show child accessibles and then hide their container"; + }; + } + + /** + * Move a node by aria-owns from right to left in the tree, so that + * the eventing looks this way: + * reorder for 't8_c1' + * hide for 't8_c1_child' + * show for 't8_c2_moved' + * reorder for 't8_c2' + * hide for 't8_c2_moved' + * + * The hide event should be delivered before the paired show event. + */ + function test8() + { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode('t8_c1_child')), + new invokerChecker(EVENT_HIDE, 't8_c2_moved'), + new invokerChecker(EVENT_SHOW, 't8_c2_moved'), + new invokerChecker(EVENT_REORDER, 't8_c2'), + new invokerChecker(EVENT_REORDER, 't8_c1'), + ]; + + this.invoke = function test8_invoke() + { + // Remove a node from 't8_c1' container to give the event tree a + // desired structure (the 't8_c1' container node goes first in the event + // tree) + getNode('t8_c1_child').remove(); + // then move 't8_c2_moved' from 't8_c2' to 't8_c1'. + getNode('t8_c1').setAttribute('aria-owns', 't8_c2_moved'); + }; + + this.getID = function test8_getID() { + return "Move a node by aria-owns to left within the tree"; + }; + } + + /** + * Move 't9_c3_moved' node under 't9_c2_moved', and then move 't9_c2_moved' + * node by aria-owns (same as test10 but has different aria-owns + * ordering), the eventing looks same way as in test10: + * reorder for 't9_c1' + * hide for 't9_c1_child' + * show for 't9_c2_moved' + * reorder for 't9_c2' + * hide for 't9_c2_child' + * hide for 't9_c2_moved' + * reorder for 't9_c3' + * hide for 't9_c3_moved' + * + * The hide events for 't9_c2_moved' and 't9_c3_moved' should be delivered + * before the show event for 't9_c2_moved'. + */ + function test9() + { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode('t9_c1_child')), + new invokerChecker(EVENT_HIDE, getNode('t9_c2_child')), + new invokerChecker(EVENT_HIDE, 't9_c3_moved'), + new invokerChecker(EVENT_HIDE, 't9_c2_moved'), + new invokerChecker(EVENT_SHOW, 't9_c2_moved'), + new invokerChecker(EVENT_REORDER, 't9_c3'), + new invokerChecker(EVENT_REORDER, 't9_c2'), + new invokerChecker(EVENT_REORDER, 't9_c1'), + new unexpectedInvokerChecker(EVENT_SHOW, 't9_c3_moved') + ]; + + this.invoke = function test9_invoke() + { + // Remove child nodes from 't9_c1' and 't9_c2' containers to give + // the event tree a needed structure ('t9_c1' and 't9_c2' nodes go + // first in the event tree), + getNode('t9_c1_child').remove(); + getNode('t9_c2_child').remove(); + // then do aria-owns magic. + getNode('t9_c2_moved').setAttribute('aria-owns', 't9_c3_moved'); + getNode('t9_c1').setAttribute('aria-owns', 't9_c2_moved'); + }; + + this.getID = function test9_getID() { + return "Move node #1 by aria-owns and then move node #2 into node #1"; + }; + } + + /** + * Move a node 't10_c3_moved' by aria-owns under a node 't10_c2_moved', + * moved by under 't10_1', so that the eventing looks this way: + * reorder for 't10_c1' + * hide for 't10_c1_child' + * show for 't10_c2_moved' + * reorder for 't10_c2' + * hide for 't10_c2_child' + * hide for 't10_c2_moved' + * reorder for 't10_c3' + * hide for 't10_c3_moved' + * + * The hide events for 't10_c2_moved' and 't10_c3_moved' should be delivered + * before the show event for 't10_c2_moved'. + */ + function test10() + { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode('t10_c1_child')), + new invokerChecker(EVENT_HIDE, getNode('t10_c2_child')), + new invokerChecker(EVENT_HIDE, getNode('t10_c2_moved')), + new invokerChecker(EVENT_HIDE, getNode('t10_c3_moved')), + new invokerChecker(EVENT_SHOW, getNode('t10_c2_moved')), + new invokerChecker(EVENT_REORDER, 't10_c2'), + new invokerChecker(EVENT_REORDER, 't10_c1'), + new invokerChecker(EVENT_REORDER, 't10_c3') + ]; + + this.invoke = function test10_invoke() + { + // Remove child nodes from 't10_c1' and 't10_c2' containers to give + // the event tree a needed structure ('t10_c1' and 't10_c2' nodes go first + // in the event tree), + getNode('t10_c1_child').remove(); + getNode('t10_c2_child').remove(); + // then do aria-owns stuff. + getNode('t10_c1').setAttribute('aria-owns', 't10_c2_moved'); + getNode('t10_c2_moved').setAttribute('aria-owns', 't10_c3_moved'); + }; + + this.getID = function test10_getID() { + return "Move a node by aria-owns into a node moved by aria-owns to left within the tree"; + }; + } + + /** + * Move a node by aria-owns from right to left in the tree, and then + * move its parent too by aria-owns. No hide event should be fired for + * original node. + */ + function test11() + { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode('t11_c1_child')), + new invokerChecker(EVENT_HIDE, getNode('t11_c2')), + new orderChecker(), + new asyncInvokerChecker(EVENT_SHOW, 't11_c2_child'), + new asyncInvokerChecker(EVENT_SHOW, 't11_c2'), + new orderChecker(), + new invokerChecker(EVENT_REORDER, 't11'), + new unexpectedInvokerChecker(EVENT_HIDE, 't11_c2_child'), + new unexpectedInvokerChecker(EVENT_REORDER, 't11_c1'), + new unexpectedInvokerChecker(EVENT_REORDER, 't11_c2'), + new unexpectedInvokerChecker(EVENT_REORDER, 't11_c3') + ]; + + this.invoke = function test11_invoke() + { + // Remove a node from 't11_c1' container to give the event tree a + // desired structure (the 't11_c1' container node goes first in + // the event tree), + getNode('t11_c1_child').remove(); + // then move 't11_c2_moved' from 't11_c2' to 't11_c1', and then move + // 't11_c2' to 't11_c3'. + getNode('t11_c1').setAttribute('aria-owns', 't11_c2_child'); + getNode('t11_c3').setAttribute('aria-owns', 't11_c2'); + }; + + this.getID = function test11_getID() { + return "Move a node by aria-owns to left within the tree"; + }; + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests. + + gA11yEventDumpToConsole = true; // debug stuff + //enableLogging("eventTree"); + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new removeChildNParent("option1", "select1")); + gQueue.push(new removeParentNChild("option2", "select2")); + gQueue.push(new hideChildNParent("option3", "select3")); + gQueue.push(new hideParentNChild("option4", "select4")); + gQueue.push(new hideChildNRemoveParent("option5", "select5")); + gQueue.push(new hideParentNRemoveChild("option6", "select6")); + gQueue.push(new removeChildNHideParent("option7", "select7")); + gQueue.push(new removeParentNHideChild("option8", "select8")); + + gQueue.push(new addParentNChild("testContainer", false)); + gQueue.push(new addParentNChild("testContainer", true)); + gQueue.push(new showParentNChild("select9", "option9", false)); + gQueue.push(new showParentNChild("select10", "option10", true)); + gQueue.push(new showParentNAddChild("select11", false)); + gQueue.push(new showParentNAddChild("select12", true)); + + gQueue.push(new removeGrandChildrenNHideParent("t1_child1", "t1_child2", "t1_parent")); + gQueue.push(new test3()); + gQueue.push(new test4()); + gQueue.push(new test5()); + gQueue.push(new test6()); + gQueue.push(new test7()); + gQueue.push(new test8()); + gQueue.push(new test9()); + gQueue.push(new test10()); + gQueue.push(new test11()); + + 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=513213" + title="coalesce events when new event is appended to the queue"> + Mozilla Bug 513213 + </a><br> + <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="testContainer"> + <select id="select1"> + <option id="option1">option</option> + </select> + <select id="select2"> + <option id="option2">option</option> + </select> + <select id="select3"> + <option id="option3">option</option> + </select> + <select id="select4"> + <option id="option4">option</option> + </select> + <select id="select5"> + <option id="option5">option</option> + </select> + <select id="select6"> + <option id="option6">option</option> + </select> + <select id="select7"> + <option id="option7">option</option> + </select> + <select id="select8"> + <option id="option8">option</option> + </select> + + <select id="select9" style="display: none"> + <option id="option9" style="display: none">testing</option> + </select> + <select id="select10" style="display: none"> + <option id="option10" style="display: none">testing</option> + </select> + <select id="select11" style="display: none"></select> + <select id="select12" style="display: none"></select> + </div> + + <div id="testContainer2"> + <div id="t1_parent"> + <div id="t1_mid1"><div id="t1_child1"></div></div> + <div id="t1_mid2"><div id="t1_child2"></div></div> + </div> + </div> + + <div id="t3"> + <div role="listbox" id="t3_lb"> + <div role="option" id="t3_o">opt</div> + </div> + </div> + + <div id="t4"> + <div role="listbox" id="t4_lb"> + <div role="option" id="t4_o1">opt1</div> + <div role="option" id="t4_o2">opt2</div> + </div> + </div> + + <div id="t5"> + <div role="button" id="t5_b">btn</div> + <div role="listbox" id="t5_lb"> + <div role="option" id="t5_o">opt</div> + </div> + </div> + + <div id="t6"> + </div> + + <div id="t7"> + <div id="t7_moveplace"></div> + <div id="t7_c"> + <div><div id="t7_c_grandchild"></div></div> + <div id="t7_c_directchild"></div> + </div> + </div> + + <div id="t8"> + <div id="t8_c1"><div id="t8_c1_child"></div></div> + <div id="t8_c2"> + <div id="t8_c2_moved"></div> + </div> + </div> + + <div id="t9"> + <div id="t9_c1"><div id="t9_c1_child"></div></div> + <div id="t9_c2"> + <div id="t9_c2_child"></div> + <div id="t9_c2_moved"></div> + </div> + <div id="t9_c3"> + <div id="t9_c3_moved"></div> + </div> + </div> + + <div id="t10"> + <div id="t10_c1"><div id="t10_c1_child"></div></div> + <div id="t10_c2"> + <div id="t10_c2_child"></div> + <div id="t10_c2_moved"></div> + </div> + <div id="t10_c3"> + <div id="t10_c3_moved"></div> + </div> + </div> + + <div id="t11"> + <div id="t11_c1"><div id="t11_c1_child"></div></div> + <div id="t11_c2"><div id="t11_c2_child"></div></div> + <div id="t11_c3"></div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_contextmenu.html b/accessible/tests/mochitest/events/test_contextmenu.html new file mode 100644 index 000000000..065e1b50e --- /dev/null +++ b/accessible/tests/mochitest/events/test_contextmenu.html @@ -0,0 +1,139 @@ +<html> + +<head> + <title>Context menu tests</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + 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="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function showContextMenu(aID) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_START, getContextMenuNode()), + ]; + + this.invoke = function showContextMenu_invoke() + { + synthesizeMouse(this.DOMNode, 4, 4, { type: "contextmenu", button: 2 }); + } + + this.getID = function showContextMenu_getID() + { + return "show context menu"; + } + } + + function selectMenuItem() + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getFocusedMenuItem) + ]; + + this.invoke = function selectMenuItem_invoke() + { + synthesizeKey("VK_DOWN", { }); + } + + this.getID = function selectMenuItem_getID() + { + return "select first menuitem"; + } + } + + function closeContextMenu(aID) + { + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_END, + getAccessible(getContextMenuNode())) + ]; + + this.invoke = function closeContextMenu_invoke() + { + synthesizeKey("VK_ESCAPE", { }); + } + + this.getID = function closeContextMenu_getID() + { + return "close context menu"; + } + } + + function getContextMenuNode() + { + return getRootAccessible().DOMDocument. + getElementById("contentAreaContextMenu"); + } + + function getFocusedMenuItem() + { + var menu = getAccessible(getAccessible(getContextMenuNode())); + for (var idx = 0; idx < menu.childCount; idx++) { + var item = menu.getChildAt(idx); + + if (hasState(item, STATE_FOCUSED)) + return getAccessible(item); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new showContextMenu("input")); + gQueue.push(new selectMenuItem()); + gQueue.push(new closeContextMenu()); + + 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=580535" + title="Broken accessibility in context menus"> + Mozilla Bug 580535 + </a><br> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_descrchange.html b/accessible/tests/mochitest/events/test_descrchange.html new file mode 100644 index 000000000..d51318532 --- /dev/null +++ b/accessible/tests/mochitest/events/test_descrchange.html @@ -0,0 +1,85 @@ +<html> + +<head> + <title>Accessible description change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function setAttr(aID, aAttr, aValue, aChecker) + { + this.eventSeq = [ aChecker ]; + this.invoke = function setAttr_invoke() + { + getNode(aID).setAttribute(aAttr, aValue); + } + + this.getID = function setAttr_getID() + { + return "set attr '" + aAttr + "', value '" + aValue + "'"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + //gA11yEventDumpToConsole = true; // debuggin + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new setAttr("tst1", "aria-describedby", "display", + new invokerChecker(EVENT_DESCRIPTION_CHANGE, "tst1"))); + gQueue.push(new setAttr("tst1", "title", "title", + new unexpectedInvokerChecker(EVENT_DESCRIPTION_CHANGE, "tst1"))); + + gQueue.push(new setAttr("tst2", "title", "title", + new invokerChecker(EVENT_NAME_CHANGE, "tst2"))); + + 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=991969" + title="Event not fired when description changes"> + Bug 991969 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <button id="tst1">btn1</button> + <button id="tst2">btn2</button> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_docload.html b/accessible/tests/mochitest/events/test_docload.html new file mode 100644 index 000000000..a530592ee --- /dev/null +++ b/accessible/tests/mochitest/events/test_docload.html @@ -0,0 +1,360 @@ +<html> + +<head> + <title>Accessible events testing for document</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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"> + // Front end stuff sometimes likes to stuff things in the hidden window(s) + // in which case there's accessibles for that content. + Components.utils.import("resource://gre/modules/Services.jsm"); + + // Force the creation of an accessible for the hidden window's document. + var doc = Services.appShell.hiddenDOMWindow.document; + gAccService.getAccessibleFor(doc); + + // The private hidden window will be lazily created that's why we need to do + // it here *before* loading '../events.js' or else we'll have a duplicate + // reorder event. + var privateDoc = Services.appShell.hiddenPrivateDOMWindow.document; + + // Force the creation of an accessible for the private hidden window's doc. + gAccService.getAccessibleFor(privateDoc); + </script> + + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function changeIframeSrc(aIdentifier, aURL) + { + this.DOMNode = getNode(aIdentifier); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getAccessible(this.DOMNode)), + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getIframeDoc) + ]; + + this.invoke = function changeIframeSrc_invoke() + { + this.DOMNode.src = aURL; + } + + this.finalCheck = function changeIframeSrc_finalCheck() + { + var accTree = { + role: ROLE_INTERNAL_FRAME, + children: [ + { + role: ROLE_DOCUMENT, + name: aURL == "about:" ? "About:" : aURL + } + ] + }; + + testAccessibleTree(this.DOMNode, accTree); + } + + this.getID = function changeIframeSrc_getID() + { + return "change iframe src on " + aURL; + } + + function getIframeDoc() + { + return getAccessible(getNode(aIdentifier).contentDocument); + } + } + + const kHide = 1; + const kShow = 2; + const kRemove = 3; + function morphIFrame(aIdentifier, aAction) + { + this.DOMNode = getNode(aIdentifier); + this.IFrameContainerDOMNode = this.DOMNode.parentNode; + + this.eventSeq = []; + + var checker = null; + if (aAction == kShow) + checker = new invokerChecker(EVENT_SHOW, this.DOMNode); + else + checker = new invokerChecker(EVENT_HIDE, this.DOMNode); + this.eventSeq.push(checker); + + var reorderChecker = + new invokerChecker(EVENT_REORDER, this.IFrameContainerDOMNode); + this.eventSeq.push(reorderChecker); + + this.invoke = function morphIFrame_invoke() + { + if (aAction == kHide) { + this.DOMNode.style.display = "none"; + } else if (aAction == kShow) { + this.DOMNode.style.display = "block"; + } else { + this.IFrameContainerDOMNode.removeChild(this.DOMNode); + } + } + + this.finalCheck = function morphIFrame_finalCheck() + { + var accTree = { + role: ROLE_SECTION, + children: (aAction == kHide || aAction == kRemove) ? [ ] : + [ + { + role: ROLE_INTERNAL_FRAME, + children: [ + { role: ROLE_DOCUMENT } + ] + } + ] + }; + + testAccessibleTree(this.IFrameContainerDOMNode, accTree); + } + + this.getID = function morphIFrame_getID() + { + if (aAction == kRemove) + return "remove iframe"; + + return "change display style of iframe to " + + ((aAction == kHide) ? "none" : "block"); + } + } + + function makeIFrameVisible(aID) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.DOMNode.parentNode) + ]; + + this.invoke = function makeIFrameVisible_invoke() + { + this.DOMNode.style.visibility = "visible"; + } + + this.getID = function makeIFrameVisible_getID() + { + return "The accessible for DOM document loaded before it's shown shouldn't have busy state."; + } + } + + function openDialogWnd(aURL) + { + // Get application root accessible. + var docAcc = getAccessible(document); + while (docAcc) { + this.mRootAcc = docAcc; + try { + docAcc = docAcc.parent; + } catch (e) { + ok(false, "Can't get parent for " + prettyName(docAcc)); + throw e; + } + } + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.mRootAcc) + ]; + + this.invoke = function openDialogWnd_invoke() + { + this.mDialog = window.openDialog(aURL); + } + + this.finalCheck = function openDialogWnd_finalCheck() + { + this.finalCheckImpl(); + } + + this.finalCheckImpl = function openDialogWnd_finalCheckImpl() + { + var accTree = { + role: ROLE_APP_ROOT, + children: [ + { + role: ROLE_CHROME_WINDOW + }, + { + role: ROLE_CHROME_WINDOW + }, + { + role: ROLE_CHROME_WINDOW + }, + { + role: ROLE_CHROME_WINDOW + } + ] + }; + + testAccessibleTree(this.mRootAcc, accTree); + + var dlgDoc = this.mDialog.document; + ok(isAccessibleInCache(dlgDoc), + "The document accessible for '" + aURL + "' is not in cache!"); + + this.mDialog.close(); + + // close() is asynchronous. + SimpleTest.executeSoon(function() { + ok(!isAccessibleInCache(dlgDoc), + "The document accessible for '" + aURL + "' is in cache still!"); + }); + } + + this.getID = function openDialogWnd_getID() + { + return "open dialog '" + aURL + "'"; + } + } + + function openWndShutdownDoc() + { + this.__proto__ = + new openDialogWnd("../events/docload_wnd.html"); + + var thisObj = this; + var docChecker = { + type: EVENT_HIDE, + get target() + { + var iframe = this.invoker.mDialog.document.getElementById("iframe"); + this.invoker.iframeDoc = iframe.contentDocument; + return iframe; + }, + get targetDescr() + { + return "inner iframe of docload_wnd.html document"; + }, + invoker: thisObj + }; + + this.eventSeq.push(docChecker); + + this.finalCheck = function openWndShutdownDoc_finalCheck() + { + // After timeout after event hide for iframe was handled the document + // accessible for iframe's document is in cache still. + ok(!isAccessibleInCache(this.iframeDoc), + "The document accessible for iframe is in cache still after iframe hide!"); + + this.finalCheckImpl(); + + // After the window is closed all alive subdocument accessibles should + // be shut down. + ok(!isAccessibleInCache(this.iframeDoc), + "The document accessible for iframe is in cache still!"); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + + // Debug stuff. + // gA11yEventDumpID = "eventdump"; + //gA11yEventDumpToConsole = true; + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new changeIframeSrc("iframe", "about:")); + gQueue.push(new changeIframeSrc("iframe", "about:buildconfig")); + gQueue.push(new morphIFrame("iframe", kHide)); + gQueue.push(new morphIFrame("iframe", kShow)); + gQueue.push(new morphIFrame("iframe", kRemove)); + gQueue.push(new makeIFrameVisible("iframe2")); + gQueue.push(new openDialogWnd("about:")); + gQueue.push(new openWndShutdownDoc()); + + gQueue.onFinish = doLastCallTests; + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + function doLastCallTests() + { + ////////////////////////////////////////////////////////////////////////// + // makeIFrameVisible() test, part2 + + // The document shouldn't have busy state (the DOM document was loaded + // before its accessible was created). Do this test lately to make sure + // the content of document accessible was created initially, prior to this + // the document accessible keeps busy state. The initial creation happens + // asynchronously after document creation, there are no events we could + // use to catch it. + var iframeDoc = getAccessible("iframe2").firstChild; + testStates(iframeDoc, 0, 0, STATE_BUSY); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=420845" + title="Fire event_reorder on any embedded frames/iframes whos document has just loaded"> + Mozilla Bug 420845 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=506206" + title="Fire event_reorder application root accessible"> + Mozilla Bug 506206 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=566103" + title="Reorganize accessible document handling"> + Mozilla Bug 566103 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=571459" + title="Shutdown document accessible when presshell goes away"> + Mozilla Bug 571459 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=658185" + title="The DOM document loaded before it's shown shouldn't have busy state"> + Mozilla Bug 658185 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=754165" + title="Fire document load events on iframes too"> + Mozilla Bug 754165 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="testContainer"><iframe id="iframe"></iframe></div> + <div id="testContainer2"><iframe id="iframe2" src="about:" style="visibility: hidden;"></iframe></div> + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_docload.xul b/accessible/tests/mochitest/events/test_docload.xul new file mode 100644 index 000000000..4b07b0e72 --- /dev/null +++ b/accessible/tests/mochitest/events/test_docload.xul @@ -0,0 +1,243 @@ +<?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="Accessibility Loading Document Events Test."> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Invoker checkers. + function stateBusyChecker(aIsEnabled) + { + this.type = EVENT_STATE_CHANGE; + this.__defineGetter__("target", currentTabDocument); + + this.check = function stateBusyChecker_check(aEvent) + { + var event = null; + try { + var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent); + } catch (e) { + ok(false, "State change event was expected"); + } + + if (!event) + return; + + is(event.state, STATE_BUSY, "Wrong state of statechange event."); + is(event.isEnabled, aIsEnabled, + "Wrong value of state of statechange event"); + + testStates(event.accessible, (aIsEnabled ? STATE_BUSY : 0), 0, + (aIsEnabled ? 0 : STATE_BUSY), 0); + } + } + + function documentReloadChecker(aIsFromUserInput) + { + this.type = EVENT_DOCUMENT_RELOAD; + this.__defineGetter__("target", currentTabDocument); + + this.check = function documentReloadChecker_check(aEvent) + { + is(aEvent.isFromUserInput, aIsFromUserInput, + "Wrong value of isFromUserInput"); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Invokers. + + /** + * Load URI. + */ + function loadURIInvoker(aURI) + { + this.invoke = function loadURIInvoker_invoke() + { + tabBrowser().loadURI(aURI); + } + + this.eventSeq = [ + // We don't expect state change event for busy true since things happen + // quickly and it's coalesced. + new asyncInvokerChecker(EVENT_REORDER, currentBrowser), + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new stateBusyChecker(false) + ]; + + this.getID = function loadURIInvoker_getID() + { + return "load uri " + aURI; + } + } + + /** + * Load the document having sub document. No document loading events for + * nested document. + */ + function loadNestedDocURIInvoker(aNestedDocURI) + { + this.__proto__ = new loadURIInvoker(aNestedDocURI); + + // Remove reorder event checker since the event is likely coalesced by + // reorder event on Firefox UI (refer to bug 759670 for details). + this.eventSeq.shift(); + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getNestedDoc), + new invokerChecker(EVENT_STATE_CHANGE, getNestedDoc) + ]; + + function getNestedDoc() + { + var iframeNodes = currentTabDocument().getElementsByTagName("iframe"); + return iframeNodes && iframeNodes.length > 0 ? + iframeNodes[0].firstChild : null; + } + } + + /** + * Reload the page by F5 (isFromUserInput flag is true). + */ + function userReloadInvoker() + { + this.invoke = function userReloadInvoker_invoke() + { + synthesizeKey("VK_F5", {}, browserWindow()); + } + + this.eventSeq = [ + new documentReloadChecker(true), + new asyncInvokerChecker(EVENT_REORDER, currentBrowser), + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new stateBusyChecker(false) + ]; + + this.getID = function userReloadInvoker_getID() + { + return "user reload page"; + } + } + + /** + * Reload the page (isFromUserInput flag is false). + */ + function reloadInvoker() + { + this.invoke = function reloadInvoker_invoke() + { + tabBrowser().reload(); + } + + this.eventSeq = [ + new documentReloadChecker(false), + new asyncInvokerChecker(EVENT_REORDER, currentBrowser), + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new stateBusyChecker(false) + ]; + + this.getID = function reloadInvoker_getID() + { + return "reload page"; + } + } + + /** + * Load wrong URI what results in error page loading. + */ + function loadErrorPageInvoker(aURL, aURLDescr) + { + this.invoke = function loadErrorPageInvoker_invoke() + { + tabBrowser().loadURI(aURL); + } + + this.eventSeq = [ + // We don't expect state change for busy true, load stopped events since + // things happen quickly and it's coalesced. + new asyncInvokerChecker(EVENT_REORDER, currentBrowser), + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new stateBusyChecker(false) + ]; + + this.getID = function loadErrorPageInvoker_getID() + { + return "load error page: '" + aURLDescr + "'"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Tests + + //gA11yEventDumpToConsole = true; // debug + //gA11yEventDumpFeature = "parentchain:reorder"; + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + var dataURL = + "data:text/html,<html><body><iframe src='http://example.com'></iframe></body></html>"; + gQueue.push(new loadNestedDocURIInvoker(dataURL)); + + gQueue.push(new loadURIInvoker("about:")); + gQueue.push(new userReloadInvoker()); + gQueue.push(new loadURIInvoker("about:mozilla")); + gQueue.push(new reloadInvoker()); + gQueue.push(new loadErrorPageInvoker("www.wronguri.wronguri", + "Server not found")); + gQueue.push(new loadErrorPageInvoker("https://nocert.example.com:443", + "Untrusted Connection")); + + gQueue.onFinish = function() { closeBrowserWindow(); } + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTests); + ]]> + </script> + + <vbox 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=566103" + title=" reorganize accessible document handling"> + Mozilla Bug 566103 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=754165" + title="Fire document load events on iframes too"> + Mozilla Bug 754165 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="eventdump"></vbox> + </vbox> +</window> diff --git a/accessible/tests/mochitest/events/test_docload_aria.html b/accessible/tests/mochitest/events/test_docload_aria.html new file mode 100644 index 000000000..c5f470aee --- /dev/null +++ b/accessible/tests/mochitest/events/test_docload_aria.html @@ -0,0 +1,83 @@ +<html> + +<head> + <title>Accessible events testing for ARIA document</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 + + function showARIADialog(aID) + { + this.dialogNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, this.dialogNode) + ]; + + this.invoke = function showARIADialog_invoke() + { + this.dialogNode.style.display = "block"; + } + + this.getID = function showARIADialog_getID() + { + return "show ARIA dialog"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + + // Debug stuff. + //gA11yEventDumpToConsole = true; + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new showARIADialog("dialog")); + gQueue.push(new showARIADialog("document")); + + 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=759833" + title="ARIA documents should fire document loading events"> + Mozilla Bug 759833 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="dialog" id="dialog" style="display: none;">It's a dialog</div> + <div role="document" id="document" style="display: none;">It's a document</div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_dragndrop.html b/accessible/tests/mochitest/events/test_dragndrop.html new file mode 100644 index 000000000..cfba80a46 --- /dev/null +++ b/accessible/tests/mochitest/events/test_dragndrop.html @@ -0,0 +1,110 @@ +<html> + +<head> + <title>Accessible drag and drop event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + + /** + * Do tests. + */ + var gQueue = null; + + // aria grabbed invoker + function changeGrabbed(aNodeOrID, aGrabValue) + { + this.DOMNode = getNode(aNodeOrID); + + this.invoke = function changeGrabbed_invoke() { + if (aGrabValue != undefined) { + this.DOMNode.setAttribute("aria-grabbed", aGrabValue); + } + } + + this.check = function changeGrabbed_check() { + testAttrs(aNodeOrID, {"grabbed" : aGrabValue}, true); + } + + this.getID = function changeGrabbed_getID() { + return prettyName(aNodeOrID) + " aria-grabbed changed"; + } + } + + // aria dropeffect invoker + function changeDropeffect(aNodeOrID, aDropeffectValue) + { + this.DOMNode = getNode(aNodeOrID); + + this.invoke = function changeDropeffect_invoke() { + if (aDropeffectValue != undefined) { + this.DOMNode.setAttribute("aria-dropeffect", aDropeffectValue); + } + } + + this.check = function changeDropeffect_check() { + testAttrs(aNodeOrID, {"dropeffect" : aDropeffectValue}, true); + } + + this.getID = function changeDropeffect_getID() { + return prettyName(aNodeOrID) + " aria-dropeffect changed"; + } + } + + function doTests() + { + // Test aria attribute mutation events + gQueue = new eventQueue(nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED); + + var id="grabbable"; + gQueue.push(new changeGrabbed(id, "true")); + gQueue.push(new changeGrabbed(id, "false")); + todo(false, "uncomment this test when 472142 is fixed."); + //gQueue.push(new changeGrabbed(id, "undefined")); + + var id="dropregion"; + gQueue.push(new changeDropeffect(id, "copy")); + gQueue.push(new changeDropeffect(id, "execute")); + + 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=510441" + title="Add support for nsIAccessibleEvent::OBJECT_ATTRIBUTE_CHANGED"> + Mozilla Bug 510441 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <!-- ARIA grabbed --> + <div id="grabbable" role="button" aria-grabbed="foo">button</div> + + <!-- ARIA dropeffect --> + <div id="dropregion" role="region" aria-dropeffect="none">button</div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_flush.html b/accessible/tests/mochitest/events/test_flush.html new file mode 100644 index 000000000..44a9afd94 --- /dev/null +++ b/accessible/tests/mochitest/events/test_flush.html @@ -0,0 +1,77 @@ +<html> + +<head> + <title>Flush delayed events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + SimpleTest.expectAssertions(0, 1); + + var gFocusHandler = { + handleEvent: function(aEvent) { + switch (this.count) { + case 0: + is(aEvent.DOMNode, getNode("input1"), + "Focus event for input1 was expected!"); + getAccessible("input2").takeFocus(); + break; + + case 1: + is(aEvent.DOMNode, getNode("input2"), + "Focus event for input2 was expected!"); + + unregisterA11yEventListener(EVENT_FOCUS, gFocusHandler); + SimpleTest.finish(); + break; + + default: + ok(false, "Wrong focus event!"); + } + + this.count++; + }, + + count: 0 + }; + + function doTests() + { + registerA11yEventListener(EVENT_FOCUS, gFocusHandler); + + getAccessible("input1").takeFocus(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=477551" + title="DocAccessible::FlushPendingEvents isn't robust"> + Mozilla Bug 477551 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input1"> + <input id="input2"> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html new file mode 100644 index 000000000..4cd57fe3b --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html @@ -0,0 +1,120 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=429547 +--> +<head> + <title>aria-activedescendant focus tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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"> + //gA11yEventDumpToConsole = true; // debugging + + function changeARIAActiveDescendant(aID, aItemID) + { + this.eventSeq = [ + new focusChecker(aItemID) + ]; + + this.invoke = function changeARIAActiveDescendant_invoke() + { + getNode(aID).setAttribute("aria-activedescendant", aItemID); + } + + this.getID = function changeARIAActiveDescendant_getID() + { + return "change aria-activedescendant on " + aItemID; + } + } + + function insertItemNFocus(aID, aNewItemID) + { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, aNewItemID), + new focusChecker(aNewItemID) + ]; + + this.invoke = function insertItemNFocus_invoke() + { + var container = getNode(aID); + var itemNode = document.createElement("div"); + itemNode.setAttribute("id", aNewItemID); + itemNode.textContent = aNewItemID; + container.appendChild(itemNode); + + container.setAttribute("aria-activedescendant", aNewItemID); + } + + this.getID = function insertItemNFocus_getID() + { + return "insert new node and focus it with ID: " + aNewItemID; + } + } + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("listbox", new focusChecker("item1"))); + gQueue.push(new changeARIAActiveDescendant("listbox", "item2")); + gQueue.push(new changeARIAActiveDescendant("listbox", "item3")); + + gQueue.push(new synthFocus("combobox_entry", new focusChecker("combobox_entry"))); + gQueue.push(new changeARIAActiveDescendant("combobox", "combobox_option2")); + + todo(false, "No focus for inserted element, bug 687011"); + //gQueue.push(new insertItemNFocus("listbox", "item4")); + + 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=429547" + title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()"> + Mozilla Bug 429547 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=761102" + title="Focus may be missed when ARIA active-descendant is changed on active composite widget"> + Mozilla Bug 761102 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="listbox" aria-activedescendant="item1" id="listbox" tabindex="1" + aria-owns="item3"> + <div role="listitem" id="item1">item1</div> + <div role="listitem" id="item2">item2</div> + </div> + <div role="listitem" id="item3">item3</div> + + <div role="combobox" id="combobox"> + <input id="combobox_entry"> + <ul> + <li role="option" id="combobox_option1">option1</li> + <li role="option" id="combobox_option2">option2</li> + </ul> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_autocomplete.xul b/accessible/tests/mochitest/events/test_focus_autocomplete.xul new file mode 100644 index 000000000..2a32a6587 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xul @@ -0,0 +1,518 @@ +<?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"?> + +<!-- Firefox searchbar --> +<?xml-stylesheet href="chrome://browser/content/browser.css" + type="text/css"?> +<!-- SeaMonkey searchbar --> +<?xml-stylesheet href="chrome://navigator/content/navigator.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible focus event testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript" + src="../autocomplete.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Hacky stuffs + + // This is the hacks needed to use a searchbar without browser.js. + function getBrowser() + { + return { + mCurrentBrowser: { engines: new Array() } + }; + } + + var BrowserSearch = { + updateOpenSearchBadge: function() {} + }; + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function loadFormAutoComplete(aIFrameID) + { + this.iframeNode = getNode(aIFrameID); + this.iframe = getAccessible(aIFrameID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.iframe) + ]; + + this.invoke = function loadFormAutoComplete_invoke() + { + var url = "data:text/html,<html><body><form id='form'>" + + "<input id='input' name='a11ytest-formautocomplete'>" + + "</form></body></html>"; + this.iframeNode.setAttribute("src", url); + } + + this.getID = function loadFormAutoComplete_getID() + { + return "load form autocomplete page"; + } + } + + function initFormAutoCompleteBy(aIFrameID, aAutoCompleteValue) + { + this.iframe = getAccessible(aIFrameID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.iframe) + ]; + + this.invoke = function initFormAutoCompleteBy_invoke() + { + var iframeDOMDoc = getIFrameDOMDoc(aIFrameID); + + var inputNode = iframeDOMDoc.getElementById("input"); + inputNode.value = aAutoCompleteValue; + var formNode = iframeDOMDoc.getElementById("form"); + formNode.submit(); + } + + this.getID = function initFormAutoCompleteBy_getID() + { + return "init form autocomplete by '" + aAutoCompleteValue + "'"; + } + } + + function loadHTML5ListAutoComplete(aIFrameID) + { + this.iframeNode = getNode(aIFrameID); + this.iframe = getAccessible(aIFrameID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.iframe) + ]; + + this.invoke = function loadHTML5ListAutoComplete_invoke() + { + var url = "data:text/html,<html><body>" + + "<datalist id='cities'><option>hello</option><option>hi</option></datalist>" + + "<input id='input' list='cities'>" + + "</body></html>"; + this.iframeNode.setAttribute("src", url); + } + + this.getID = function loadHTML5ListAutoComplete_getID() + { + return "load HTML5 list autocomplete page"; + } + } + + function removeChar(aID, aCheckerOrEventSeq) + { + this.__proto__ = new synthAction(aID, aCheckerOrEventSeq); + + this.invoke = function removeChar_invoke() + { + synthesizeKey("VK_LEFT", { shiftKey: true }); + synthesizeKey("VK_DELETE", {}); + } + + this.getID = function removeChar_getID() + { + return "remove char on " + prettyName(aID); + } + } + + function replaceOnChar(aID, aChar, aCheckerOrEventSeq) + { + this.__proto__ = new synthAction(aID, aCheckerOrEventSeq); + + this.invoke = function replaceOnChar_invoke() + { + this.DOMNode.select(); + synthesizeKey(aChar, {}); + } + + this.getID = function replaceOnChar_getID() + { + return "replace on char '" + aChar + "' for" + prettyName(aID); + } + } + + function focusOnMouseOver(aIDFunc, aIDFuncArg) + { + this.eventSeq = [ new focusChecker(aIDFunc, aIDFuncArg) ]; + + this.invoke = function focusOnMouseOver_invoke() + { + this.id = aIDFunc.call(null, aIDFuncArg); + this.node = getNode(this.id); + this.window = this.node.ownerDocument.defaultView; + + if (this.node.localName == "tree") { + var tree = getAccessible(this.node); + var accessible = getAccessible(this.id); + if (tree != accessible) { + var itemX = {}, itemY = {}, treeX = {}, treeY = {}; + accessible.getBounds(itemX, itemY, {}, {}); + tree.getBounds(treeX, treeY, {}, {}); + this.x = itemX.value - treeX.value; + this.y = itemY.value - treeY.value; + } + } + + // Generate mouse move events in timeouts until autocomplete popup list + // doesn't have it, the reason is do that because autocomplete popup + // ignores mousemove events firing in too short range. + synthesizeMouse(this.node, this.x, this.y, { type: "mousemove" }); + this.doMouseMoveFlood(this); + } + + this.finalCheck = function focusOnMouseOver_getID() + { + this.isFlooding = false; + } + + this.getID = function focusOnMouseOver_getID() + { + return "mouse over on " + prettyName(aIDFunc.call(null, aIDFuncArg)); + } + + this.doMouseMoveFlood = function focusOnMouseOver_doMouseMoveFlood(aThis) + { + synthesizeMouse(aThis.node, aThis.x + 1, aThis.y + 1, + { type: "mousemove" }, aThis.window); + + if (aThis.isFlooding) + aThis.window.setTimeout(aThis.doMouseMoveFlood, 0, aThis); + } + + this.id = null; + this.node = null; + this.window = null; + + this.isFlooding = true; + this.x = 1; + this.y = 1; + } + + function selectByClick(aIDFunc, aIDFuncArg, + aFocusTargetFunc, aFocusTargetFuncArg) + { + this.eventSeq = [ new focusChecker(aFocusTargetFunc, aFocusTargetFuncArg) ]; + + this.invoke = function selectByClick_invoke() + { + var id = aIDFunc.call(null, aIDFuncArg); + var node = getNode(id); + var targetWindow = node.ownerDocument.defaultView; + + var x = 0, y = 0; + if (node.localName == "tree") { + var tree = getAccessible(node); + var accessible = getAccessible(id); + if (tree != accessible) { + var itemX = {}, itemY = {}, treeX = {}, treeY = {}; + accessible.getBounds(itemX, itemY, {}, {}); + tree.getBounds(treeX, treeY, {}, {}); + x = itemX.value - treeX.value; + y = itemY.value - treeY.value; + } + } + + synthesizeMouseAtCenter(node, {}, targetWindow); + } + + this.getID = function selectByClick_getID() + { + return "select by click " + prettyName(aIDFunc.call(null, aIDFuncArg)); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Target getters + + function getItem(aItemObj) + { + var autocomplete = aItemObj.autocomplete; + var autocompleteNode = aItemObj.autocompleteNode; + + // XUL searchbar + if (autocompleteNode.localName == "searchbar") { + var popupNode = autocompleteNode._popup; + if (popupNode) { + var list = getAccessible(popupNode); + return list.getChildAt(aItemObj.index); + } + } + + // XUL autocomplete + var popupNode = autocompleteNode.popup; + if (!popupNode) { + // HTML form autocomplete + var controller = Components.classes["@mozilla.org/autocomplete/controller;1"]. + getService(Components.interfaces.nsIAutoCompleteController); + popupNode = controller.input.popup.QueryInterface(nsIDOMNode); + } + + if (popupNode) { + if ("richlistbox" in popupNode) { + var list = getAccessible(popupNode.richlistbox); + return list.getChildAt(aItemObj.index); + } + + var list = getAccessible(popupNode.tree); + return list.getChildAt(aItemObj.index + 1); + } + } + + function getTextEntry(aID) + { + // For form autocompletes the autocomplete widget and text entry widget + // is the same widget, for XUL autocompletes the text entry is a first + // child. + var localName = getNode(aID).localName; + + // XUL autocomplete + if (localName == "textbox") + return getAccessible(aID).firstChild; + + // HTML form autocomplete + if (localName == "input") + return getAccessible(aID); + + // XUL searchbar + if (localName == "searchbar") + return getAccessible(getNode(aID).textbox.inputField); + + return null; + } + + function itemObj(aID, aIdx) + { + this.autocompleteNode = getNode(aID); + + this.autocomplete = this.autocompleteNode.localName == "searchbar" ? + getAccessible(this.autocompleteNode.textbox) : + getAccessible(this.autocompleteNode); + + this.index = aIdx; + } + + function getIFrameDOMDoc(aIFrameID) + { + return getNode(aIFrameID).contentDocument; + } + + //////////////////////////////////////////////////////////////////////////// + // Test helpers + + function queueAutoCompleteTests(aID) + { + // focus autocomplete text entry + gQueue.push(new synthFocus(aID, new focusChecker(getTextEntry, aID))); + + // open autocomplete popup + gQueue.push(new synthDownKey(aID, new nofocusChecker())); + + // select second option ('hi' option), focus on it + gQueue.push(new synthUpKey(aID, + new focusChecker(getItem, new itemObj(aID, 1)))); + + // choose selected option, focus on text entry + gQueue.push(new synthEnterKey(aID, new focusChecker(getTextEntry, aID))); + + // remove char, autocomplete popup appears + gQueue.push(new removeChar(aID, new nofocusChecker())); + + // select first option ('hello' option), focus on it + gQueue.push(new synthDownKey(aID, + new focusChecker(getItem, new itemObj(aID, 0)))); + + // mouse move on second option ('hi' option), focus on it + gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 1))); + + // autocomplete popup updated (no selected item), focus on textentry + gQueue.push(new synthKey(aID, "e", null, new focusChecker(getTextEntry, aID))); + + // select first option ('hello' option), focus on it + gQueue.push(new synthDownKey(aID, + new focusChecker(getItem, new itemObj(aID, 0)))); + + // popup gets hidden, focus on textentry + gQueue.push(new synthRightKey(aID, new focusChecker(getTextEntry, aID))); + + // popup gets open, no focus + gQueue.push(new synthOpenComboboxKey(aID, new nofocusChecker())); + + // select first option again ('hello' option), focus on it + gQueue.push(new synthDownKey(aID, + new focusChecker(getItem, new itemObj(aID, 0)))); + + // no option is selected, focus on text entry + gQueue.push(new synthUpKey(aID, new focusChecker(getTextEntry, aID))); + + // close popup, no focus + gQueue.push(new synthEscapeKey(aID, new nofocusChecker())); + + // autocomplete popup appears (no selected item), focus stays on textentry + gQueue.push(new replaceOnChar(aID, "h", new nofocusChecker())); + + // mouse move on first option ('hello' option), focus on it + gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 0))); + + // click first option ('hello' option), popup closes, focus on text entry + gQueue.push(new selectByClick(getItem, new itemObj(aID, 0), getTextEntry, aID)); + } + + //////////////////////////////////////////////////////////////////////////// + // Tests + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + var gInitQueue = null; + function initTests() + { + if (SEAMONKEY || MAC) { + todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237), and on Mac (bug 746177)"); + shutdownAutoComplete(); + SimpleTest.finish(); + return; + } + + gInitQueue = new eventQueue(); + gInitQueue.push(new loadFormAutoComplete("iframe")); + gInitQueue.push(new initFormAutoCompleteBy("iframe", "hello")); + gInitQueue.push(new initFormAutoCompleteBy("iframe", "hi")); + gInitQueue.push(new loadHTML5ListAutoComplete("iframe2")); + gInitQueue.onFinish = function initQueue_onFinish() + { + SimpleTest.executeSoon(doTests); + return DO_NOT_FINISH_TEST; + } + gInitQueue.invoke(); + } + + var gQueue = null; + function doTests() + { + // Test focus events. + gQueue = new eventQueue(); + + //////////////////////////////////////////////////////////////////////////// + // tree popup autocomplete tests + queueAutoCompleteTests("autocomplete"); + + //////////////////////////////////////////////////////////////////////////// + // richlistbox popup autocomplete tests + queueAutoCompleteTests("richautocomplete"); + + //////////////////////////////////////////////////////////////////////////// + // HTML form autocomplete tests + queueAutoCompleteTests(getIFrameDOMDoc("iframe").getElementById("input")); + + //////////////////////////////////////////////////////////////////////////// + // HTML5 list autocomplete tests + queueAutoCompleteTests(getIFrameDOMDoc("iframe2").getElementById("input")); + + //////////////////////////////////////////////////////////////////////////// + // searchbar tests + + // focus searchbar, focus on text entry + gQueue.push(new synthFocus("searchbar", + new focusChecker(getTextEntry, "searchbar"))); + // open search engine popup, no focus + gQueue.push(new synthOpenComboboxKey("searchbar", new nofocusChecker())); + // select first item, focus on it + gQueue.push(new synthDownKey("searchbar", + new focusChecker(getItem, new itemObj("searchbar", 0)))); + // mouse over on second item, focus on it + gQueue.push(new focusOnMouseOver(getItem, new itemObj("searchbar", 1))); + // press enter key, focus on text entry + gQueue.push(new synthEnterKey("searchbar", + new focusChecker(getTextEntry, "searchbar"))); + // click on search button, open popup, focus goes to document + var searchBtn = getAccessible(getNode("searchbar").searchButton); + gQueue.push(new synthClick(searchBtn, new focusChecker(document))); + // select first item, focus on it + gQueue.push(new synthDownKey("searchbar", + new focusChecker(getItem, new itemObj("searchbar", 0)))); + // close popup, focus goes on document + gQueue.push(new synthEscapeKey("searchbar", new focusChecker(document))); + + gQueue.onFinish = function() + { + // unregister 'test-a11y-search' autocomplete search + shutdownAutoComplete(); + } + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + // Register 'test-a11y-search' autocomplete search. + // XPFE AutoComplete needs to register early. + initAutoComplete([ "hello", "hi" ], + [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]); + + addA11yLoadEvent(initTests); + ]]> + </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=383759" + title="Focus event inconsistent for search box autocomplete"> + Mozilla Bug 383759 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559766" + title="Add accessibility support for @list on HTML input and for HTML datalist"> + Mozilla Bug 559766 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <textbox id="autocomplete" type="autocomplete" + autocompletesearch="test-a11y-search"/> + + <textbox id="richautocomplete" type="autocomplete" + autocompletesearch="test-a11y-search" + autocompletepopup="richpopup"/> + <panel id="richpopup" type="autocomplete-richlistbox" noautofocus="true"/> + + <iframe id="iframe"/> + + <iframe id="iframe2"/> + + <searchbar id="searchbar"/> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_browserui.xul b/accessible/tests/mochitest/events/test_focus_browserui.xul new file mode 100644 index 000000000..bd621ebf2 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_browserui.xul @@ -0,0 +1,149 @@ +<?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="Accessibility Loading Document Events Test."> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Helpers + + function inputInDocument() + { + var tabdoc = currentTabDocument(); + return tabdoc.getElementById("input"); + } + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function loadURI(aURI) + { + this.invoke = function loadURI_invoke() + { + tabBrowser().loadURI(aURI); + } + + this.eventSeq = [ + new focusChecker(currentTabDocument) + ]; + + this.getID = function loadURI_getID() + { + return "load uri " + aURI; + } + } + + function goBack() + { + this.invoke = function goBack_invoke() + { + tabBrowser().goBack(); + } + + this.eventSeq = [ + new focusChecker(inputInDocument) + ]; + + this.getID = function goBack_getID() + { + return "go back one page in history "; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Testing + + var gInputDocURI = "data:text/html,<html><input id='input'></html>"; + var gButtonDocURI = "data:text/html,<html><input id='input' type='button' value='button'></html>"; + + //gA11yEventDumpToConsole = true; // debug + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + var tabDocument = currentTabDocument(); + var input = inputInDocument(); + + // move focus to input inside tab document + gQueue.push(new synthTab(tabDocument, new focusChecker(input), + browserWindow())); + + // open new url, focus moves to new document + gQueue.push(new loadURI(gButtonDocURI)); + + // back one page in history, moves moves on input of tab document + gQueue.push(new goBack()); + + // open new tab, focus moves to urlbar + gQueue.push(new synthKey(tabDocument, "t", { accelKey: true, window: browserWindow() }, + new focusChecker(urlbarInput))); + + // close open tab, focus goes on input of tab document + gQueue.push(new synthKey(tabDocument, "w", { accelKey: true, window: browserWindow() }, + new focusChecker(inputInDocument))); + + gQueue.onFinish = function() + { + closeBrowserWindow(); + } + gQueue.invoke(); + } + + if (navigator.oscpu.startsWith("Windows NT 6.1") || navigator.oscpu.startsWith("Windows NT 6.2")) { + todo(false, "fix the leak!"); + } else { + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTests, gInputDocURI); + } + ]]> + </script> + + <vbox 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=644452" + title="Focus not set when switching to cached document with back or forward if anything other than the document was last focused"> + Mozilla Bug 644452 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=665412" + title="Broken focus when returning to editable text field after closing a tab while focused in the Navigation toolbar"> + Mozilla Bug 665412 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="eventdump"></vbox> + </vbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_canvas.html b/accessible/tests/mochitest/events/test_focus_canvas.html new file mode 100644 index 000000000..dcccb08e0 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_canvas.html @@ -0,0 +1,61 @@ +<html> + +<head> + <title>Accessible focus testing in canvas subdom</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + //gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("button")); + gQueue.push(new synthTab("button", new focusChecker("textbox"))); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </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> + <input id="button" type="button"> + <input id="textbox"> + </canvas> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_contextmenu.xul b/accessible/tests/mochitest/events/test_focus_contextmenu.xul new file mode 100644 index 000000000..0cf62b912 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_contextmenu.xul @@ -0,0 +1,99 @@ +<?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="Context nenu focus testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + var winLowerThanVista = navigator.platform.indexOf("Win") == 0; + if (winLowerThanVista) { + var version = Components.classes["@mozilla.org/system-info;1"] + .getService(Components.interfaces.nsIPropertyBag2) + .getProperty("version"); + version = parseFloat(version); + winLowerThanVista = !(version >= 6.0); + } + + var gQueue = null; + function doTests() + { + // bug 746183 - Whole file times out on OS X + if (MAC || winLowerThanVista) { + todo(false, "Reenable on mac after fixing bug 746183!"); + SimpleTest.finish(); + return; + } + + // Test focus events. + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("button")); + gQueue.push(new synthContextMenu("button", + new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu"))); + gQueue.push(new synthEscapeKey("contextmenu", new focusChecker("button"))); + + gQueue.push(new synthContextMenu("button", + new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu"))); + gQueue.push(new synthDownKey("contextmenu", new focusChecker("item1"))); + gQueue.push(new synthDownKey("item1", new focusChecker("item2"))); + gQueue.push(new synthRightKey("item2", new focusChecker("item2.1"))); + if (WIN) { + todo(false, "synthEscapeKey for item2.1 and item2 disabled due to bug 691580"); + } else { + gQueue.push(new synthEscapeKey("item2.1", new focusChecker("item2"))); + gQueue.push(new synthEscapeKey("item2", new focusChecker("button"))); + } + 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=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <button id="button" context="contextmenu" label="button"/> + <menupopup id="contextmenu"> + <menuitem id="item1" label="item1"/> + <menu id="item2" label="item2"> + <menupopup> + <menuitem id="item2.1" label="item2.1"/> + </menupopup> + </menu> + </menupopup> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_controls.html b/accessible/tests/mochitest/events/test_focus_controls.html new file mode 100644 index 000000000..a18832a8f --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_controls.html @@ -0,0 +1,75 @@ +<html> + +<head> + <title>Accessible focus testing on HTML controls</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + //gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(EVENT_FOCUS); + + gQueue.push(new synthFocus("textbox")); + gQueue.push(new synthFocus("textarea")); + gQueue.push(new synthFocus("button1")); + gQueue.push(new synthFocus("button2")); + gQueue.push(new synthFocus("checkbox")); + gQueue.push(new synthFocus("radio1")); + gQueue.push(new synthDownKey("radio1", new focusChecker("radio2"))); + + // no focus events for checkbox or radio inputs when they are checked + // programmatically + gQueue.push(new changeCurrentItem("checkbox")); + gQueue.push(new changeCurrentItem("radio1")); + + 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=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="textbox"> + <textarea id="textarea"></textarea> + + <input id="button1" type="button" value="button"> + <button id="button2">button</button> + <input id="checkbox" type="checkbox"> + <input id="radio1" type="radio" name="radiogroup"> + <input id="radio2" type="radio" name="radiogroup"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_dialog.html b/accessible/tests/mochitest/events/test_focus_dialog.html new file mode 100644 index 000000000..9d88b0cb4 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_dialog.html @@ -0,0 +1,164 @@ +<html> + +<head> + <title>Accessible focus testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function openCloseDialog(aID) + { + this.eventSeq = [ + new focusChecker(getNode(aID)) + ]; + + this.invoke = function openCloseDialog_invoke() + { + var wnd = window.open("focus.html"); + wnd.close(); + } + + this.getID = function openCloseDialog_getID() + { + return "Open close dialog while focus on " + prettyName(aID); + } + } + + var gDialogWnd = null; + function getDialogDocument() + { + return gDialogWnd.document; + } + + function openDialog(aID) + { + this.eventSeq = [ + new focusChecker(getDialogDocument) + ]; + + this.invoke = function openDialog_invoke() + { + gDialogWnd = window.open("focus.html"); + } + + this.getID = function openDialog_getID() + { + return "Open dialog while focus on " + prettyName(aID); + } + } + + function closeDialog(aID) + { + this.eventSeq = [ + new focusChecker(aID) + ]; + + this.invoke = function closeDialog_invoke() + { + gDialogWnd.close(); + } + + this.getID = function closeDialog_getID() + { + return "Close dialog while focus on " + prettyName(aID); + } + } + + function showNFocusAlertDialog() + { + this.ID = "alertdialog"; + this.DOMNode = getNode(this.ID); + + this.invoke = function showNFocusAlertDialog_invoke() + { + document.getElementById(this.ID).style.display = 'block'; + document.getElementById(this.ID).focus(); + } + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.DOMNode), + new focusChecker(this.DOMNode) + ]; + + this.getID = function showNFocusAlertDialog_getID() + { + return "Show and focus alert dialog " + prettyName(this.ID); + } + } + + /** + * Do tests. + */ + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTests() + { + gQueue = new eventQueue(EVENT_FOCUS); + + gQueue.push(new synthFocus("button")); + gQueue.push(new openDialog("button")); + gQueue.push(new closeDialog("button")); + + var frameNode = getNode("editabledoc"); + gQueue.push(new synthFocusOnFrame(frameNode)); + gQueue.push(new openCloseDialog(frameNode.contentDocument)); + + gQueue.push(new showNFocusAlertDialog()); + + 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=551679" + title="focus is not fired for focused document when switching between windows"> + Mozilla Bug 551679 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=580464" + title="Accessible focus incorrect after JS focus() but correct after switching apps or using menu bar"> + Mozilla Bug 580464 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <button id="button">button</button> + <iframe id="editabledoc" src="focus.html"></iframe> + + <div id="alertdialog" style="display: none" tabindex="-1" role="alertdialog" aria-labelledby="title2" aria-describedby="desc2"> + <div id="title2">Blah blah</div> + <div id="desc2">Woof woof woof.</div> + <button>Close</button> + </div> + + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_doc.html b/accessible/tests/mochitest/events/test_focus_doc.html new file mode 100644 index 000000000..bd4934d84 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_doc.html @@ -0,0 +1,95 @@ +<html> + +<head> + <title>Accessible document focus event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + var gQueue = null; + + //var gA11yEventDumpID = "eventdump"; + //gA11yEventDumpToConsole = true; + + function doTests() + { + // setup + var frameDoc = document.getElementById("iframe").contentDocument; + frameDoc.designMode = "on"; + var frameDocAcc = getAccessible(frameDoc, [nsIAccessibleDocument]); + var buttonAcc = getAccessible("b1"); + + var frame2Doc = document.getElementById("iframe2").contentDocument; + var frame2Input = frame2Doc.getElementById("input"); + var frame2DocAcc = getAccessible(frame2Doc); + var frame2InputAcc = getAccessible(frame2Input); + + // Test focus events. + gQueue = new eventQueue(); + + // try to give focus to contentEditable frame twice to cover bug 512059 + gQueue.push(new synthFocus(buttonAcc)); + gQueue.push(new synthTab(frameDocAcc, new focusChecker(frameDocAcc))); + gQueue.push(new synthFocus(buttonAcc)); + gQueue.push(new synthTab(frameDocAcc, new focusChecker(frameDocAcc))); + + // focus on not editable document + gQueue.push(new synthFocus(frame2InputAcc)); + gQueue.push(new synthShiftTab(frame2DocAcc, new focusChecker(frame2DocAcc))); + + 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=512058" + title="Can't set focus to designMode document via accessibility APIs"> + Mozilla Bug 512058 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=512059" + title="Accessibility focus event never fired for designMode document after the first focus"> + Mozilla Bug 512059 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=618046" + title="No focus change event when Shift+Tab at top of screen"> + Mozilla Bug 618046 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="eventdump"></div> + + <div id="testContainer"> + <button id="b1">a button</button> + <iframe id="iframe" src="about:blank"></iframe> + <button id="b2">a button</button> + <iframe id="iframe2" src="data:text/html,<html><input id='input'></html>"></iframe> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_general.html b/accessible/tests/mochitest/events/test_focus_general.html new file mode 100644 index 000000000..e881a5a4f --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_general.html @@ -0,0 +1,179 @@ +<html> + +<head> + <title>Accessible focus testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + 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" + src="../states.js"></script> + + <script type="application/javascript"> + function focusElmWhileSubdocIsFocused(aID) + { + this.DOMNode = getNode(aID); + + this.invoke = function focusElmWhileSubdocIsFocused_invoke() + { + this.DOMNode.focus(); + } + + this.eventSeq = [ + new focusChecker(this.DOMNode) + ]; + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_FOCUS, this.DOMNode.ownerDocument) + ]; + + this.getID = function focusElmWhileSubdocIsFocused_getID() + { + return "Focus element while subdocument is focused " + prettyName(aID); + } + } + + function imageMapChecker(aID) + { + var node = getNode(aID); + this.type = EVENT_FOCUS; + this.match = function imageMapChecker_match(aEvent) + { + return aEvent.DOMNode == node; + } + } + + function topMenuChecker() + { + this.type = EVENT_FOCUS; + this.match = function topMenuChecker_match(aEvent) + { + return aEvent.accessible.role == ROLE_PARENT_MENUITEM; + } + } + + function contextMenuChecker() + { + this.type = EVENT_MENUPOPUP_START; + this.match = function contextMenuChecker_match(aEvent) + { + return aEvent.accessible.role == ROLE_MENUPOPUP; + } + } + + function focusContextMenuItemChecker() + { + this.__proto__ = new focusChecker(); + + this.match = function focusContextMenuItemChecker_match(aEvent) + { + return aEvent.accessible.role == ROLE_MENUITEM; + } + } + + /** + * Do tests. + */ + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTests() + { + var frameDoc = document.getElementById("iframe").contentDocument; + + var editableDoc = document.getElementById('editabledoc').contentDocument; + editableDoc.designMode = 'on'; + + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("editablearea")); + gQueue.push(new synthFocus("navarea")); + gQueue.push(new synthTab("navarea", new focusChecker(frameDoc))); + gQueue.push(new focusElmWhileSubdocIsFocused("link")); + + gQueue.push(new synthTab(editableDoc, new focusChecker(editableDoc))); + if (WIN || LINUX) { + // Alt key is used to active menubar and focus menu item on Windows, + // other platforms requires setting a ui.key.menuAccessKeyFocuses + // preference. + gQueue.push(new toggleTopMenu(editableDoc, new topMenuChecker())); + gQueue.push(new toggleTopMenu(editableDoc, new focusChecker(editableDoc))); + } + gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker())); + gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker())); + gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc))); + if (SEAMONKEY) { + todo(false, "shift tab from editable document fails on (Windows) SeaMonkey! (Bug 718235)"); + } else { + if (LINUX || MAC) + todo(false, "shift tab from editable document fails on linux and Mac, bug 746519!"); + else + gQueue.push(new synthShiftTab("link", new focusChecker("link"))); + } // ! SEAMONKEY + + gQueue.push(new synthFocus("a", new imageMapChecker("a"))); + gQueue.push(new synthFocus("b", new imageMapChecker("b"))); + + 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=352220" + title="Inconsistent focus events when returning to a document frame"> + Mozilla Bug 352220 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=550338" + title="Broken focus when returning to editable documents from menus"> + Mozilla Bug 550338 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=961696" + title="Accessible object:state-changed:focused events for imagemap links are broken"> + Mozilla Bug 961696 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="editablearea" contentEditable="true">editable area</div> + <div id="navarea" tabindex="0">navigable area</div> + <iframe id="iframe" src="data:text/html,<html></html>"></iframe> + <a id="link" href="">link</a> + <iframe id="editabledoc" src="about:blank"></iframe> + + <map name="atoz_map"> + <area id="a" coords="0,0,13,14" shape="rect"> + <area id="b" coords="17,0,30,14" shape="rect"> + </map> + <img width="447" height="15" usemap="#atoz_map" src="../letters.gif"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_general.xul b/accessible/tests/mochitest/events/test_focus_general.xul new file mode 100644 index 000000000..f72834f39 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_general.xul @@ -0,0 +1,179 @@ +<?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 focus event testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + function getColorBtn(aBtnObj) + { + var colorpicker = aBtnObj.colorpicker; + var container = colorpicker.firstChild; + var btn = container.getChildAt(aBtnObj.btnIndex); + return btn; + } + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTests() + { + // Test focus events. + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("textbox", + new focusChecker(getNode("textbox").inputField))); + gQueue.push(new synthFocus("textbox_multiline", + new focusChecker(getNode("textbox_multiline").inputField))); + gQueue.push(new synthFocus("scale")); + gQueue.push(new synthFocusOnFrame("editabledoc")); + gQueue.push(new synthFocus("radioclothes", + new focusChecker("radiosweater"))); + gQueue.push(new synthDownKey("radiosweater", + new focusChecker("radiojacket"))); + gQueue.push(new synthFocus("checkbox")); + gQueue.push(new synthFocus("button")); + gQueue.push(new synthFocus("checkbutton")); + gQueue.push(new synthFocus("radiobutton")); + + // focus menubutton + gQueue.push(new synthFocus("menubutton")); + // click menubutton, open popup, focus stays on menu button + gQueue.push(new synthClick("menubutton", new nofocusChecker())); + // select first menu item ("item 1"), focus on menu item + gQueue.push(new synthDownKey("menubutton", new focusChecker("mb_item1"))); + // choose select menu item, focus gets back to menubutton + gQueue.push(new synthEnterKey("mb_item1", new focusChecker("menubutton"))); + // press enter to open popup, focus stays on menubutton + gQueue.push(new synthEnterKey("menubutton", new nofocusChecker())); + // select second menu item ("item 2"), focus on menu item + gQueue.push(new synthUpKey("menubutton", new focusChecker("mb_item2"))); + + // clicking on button having associated popup doesn't change a focus + gQueue.push(new synthClick("popupbutton", new nofocusChecker())); + // select first menu item ("item 1"), focus on menu item + gQueue.push(new synthDownKey("popupbutton", new focusChecker("bp_item1"))); + // choose select menu item, focus gets back to menubutton + gQueue.push(new synthEnterKey("bp_item1", new focusChecker("menubutton"))); + // show popup again for the next test + gQueue.push(new synthClick("popupbutton", new nofocusChecker())); + +if (!MAC) { + // click menubutton of the 'menubutton' button while popup of button open. + gQueue.push(new synthClick("mbb", new focusChecker("mbb"), { where: "right" })); + // close popup, focus stays on menubutton, fire focus event + gQueue.push(new synthEscapeKey("mbb", new focusChecker("mbb"))); + // click menubutton, open popup, focus stays on menubutton + gQueue.push(new synthClick("mbb", new nofocusChecker(), { where: "right" })); + // select first menu item ("item 1"), focus on menu item + gQueue.push(new synthDownKey("mbb", new focusChecker("mbb_item1"))); + // choose select menu item, focus gets back to menubutton + gQueue.push(new synthEnterKey("mbb_item1", new focusChecker("mbb"))); + // open popup, focus stays on menubutton + gQueue.push(new synthOpenComboboxKey("mbb", new nofocusChecker())); + // select second menu item ("item 2"), focus on menu item + gQueue.push(new synthUpKey("menubutton", new focusChecker("mbb_item2"))); + // click on menu item of menubutton menu, focus menubutton + gQueue.push(new synthClick("mbb_item2", new focusChecker("mbb"))); +} else { + todo(false, "mbb tests time out on OS X, fix bug 746970 and reenable!"); +} + + // focus colorpicker button + gQueue.push(new synthFocus("colorpicker")); + // click on button, open popup, focus goes to current color button + var btnObj = { colorpicker: getAccessible("colorpicker"), btnIndex: 0 }; + var checker = new focusChecker(getColorBtn, btnObj); + gQueue.push(new synthClick("colorpicker", checker)); + // select sibling color button, focus on it + btnObj = { colorpicker: getAccessible("colorpicker"), btnIndex: 1 }; + var checker = new focusChecker(getColorBtn, btnObj); + gQueue.push(new synthRightKey("colorpicker", checker)); + // choose selected color button, close popup, focus on colorpicker button + gQueue.push(new synthEnterKey("colorpicker", new focusChecker("colorpicker"))); + + 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=492518" + title="xul:slider accessible of xul:scale is accessible illegally"> + Mozilla Bug 492518 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368" + title=" fire focus event on document accessible whenever the root or body element is focused"> + Mozilla Bug 552368 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <textbox id="textbox" value="hello"/> + <textbox id="textbox_multiline" multiline="true" value="hello"/> + <scale id="scale" min="0" max="9" value="5"/> + <iframe id="editabledoc" src="focus.html"/> + <radiogroup id="radioclothes"> + <radio id="radiosweater" label="radiosweater"/> + <radio id="radiocap" label="radiocap" disabled="true"/> + <radio id="radiojacket" label="radiojacket"/> + </radiogroup> + <checkbox id="checkbox" label="checkbox"/> + <button id="button" label="button"/> + <button id="checkbutton" type="checkbox" label="checkbutton"/> + <button id="radiobutton" type="radio" group="rbgroup" label="radio1"/> + + <button id="menubutton" type="menu" label="menubutton"> + <menupopup> + <menuitem id="mb_item1" label="item1"/> + <menuitem id="mb_item2" label="item2"/> + </menupopup> + </button> + <button id="mbb" type="menu-button" label="menubutton button"> + <menupopup> + <menuitem id="mbb_item1" label="item1"/> + <menuitem id="mbb_item2" label="item2"/> + </menupopup> + </button> + + <colorpicker id="colorpicker" type="button" label="color picker" + color="#FFFFFF"/> + + <popupset> + <menupopup id="backpopup" position="after_start"> + <menuitem id="bp_item1" label="Page 1"/> + <menuitem id="bp_item2" label="Page 2"/> + </menupopup> + </popupset> + <button id="popupbutton" label="Pop Me Up" popup="backpopup"/> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_listcontrols.xul b/accessible/tests/mochitest/events/test_focus_listcontrols.xul new file mode 100644 index 000000000..db45048e2 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_listcontrols.xul @@ -0,0 +1,189 @@ +<?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 focus event testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + //gA11yEventDumpID = "eventdump"; // debug stuff + gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTests() + { + // Test focus events. + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("listbox", new focusChecker("lb_item1"))); + gQueue.push(new synthDownKey("lb_item1", new focusChecker("lb_item2"))); + gQueue.push(new synthTab("lb_item2", new focusChecker("mslb_item1"))); + gQueue.push(new synthDownKey("mslb_item1", new focusChecker("mslb_item2"), { shiftKey: true })); + gQueue.push(new synthTab("mslb_item2", new focusChecker("emptylistbox"))); + gQueue.push(new synthFocus("mcolumnlistbox", new focusChecker("mclb_item1"))); + gQueue.push(new synthDownKey("mclb_item1", new focusChecker("mclb_item2"))); + gQueue.push(new synthFocus("headerlistbox", new focusChecker("hlb_item1"))); + gQueue.push(new synthDownKey("hlb_item1", new focusChecker("hlb_item2"))); + + gQueue.push(new synthFocus("richlistbox", new focusChecker("rlb_item1"))); + gQueue.push(new synthDownKey("rlb_item1", new focusChecker("rlb_item2"))); + gQueue.push(new synthFocus("multiselrichlistbox", new focusChecker("msrlb_item1"))); + gQueue.push(new synthDownKey("msrlb_item1", new focusChecker("msrlb_item2"), { shiftKey: true })); + gQueue.push(new synthFocus("emptyrichlistbox", new focusChecker("emptyrichlistbox"))); + + gQueue.push(new synthFocus("menulist")); + gQueue.push(new synthClick("menulist", new focusChecker("ml_tangerine"))); + gQueue.push(new synthDownKey("ml_tangerine", new focusChecker("ml_marmalade"))); + gQueue.push(new synthEscapeKey("ml_marmalade", new focusChecker("menulist"))); +if (!MAC) { + // On Windows, items get selected during navigation. + let expectedItem = WIN ? "ml_tangerine" : "ml_marmalade"; + gQueue.push(new synthDownKey("menulist", new nofocusChecker(expectedItem))); + gQueue.push(new synthOpenComboboxKey("menulist", new focusChecker(expectedItem))); + gQueue.push(new synthEnterKey(expectedItem, new focusChecker("menulist"))); +} else { + todo(false, "Bug 746531 - timeouts of last three menulist tests on OS X"); +} + + var textentry = getAccessible("emenulist").firstChild; + gQueue.push(new synthFocus("emenulist", new focusChecker(textentry))); + gQueue.push(new synthDownKey(textentry, new nofocusChecker("eml_tangerine"))); + gQueue.push(new synthUpKey(textentry, new focusChecker("eml_marmalade"))); + gQueue.push(new synthEnterKey("eml_marmalade", new focusChecker(textentry))); + gQueue.push(new synthOpenComboboxKey("emenulist", new focusChecker("eml_marmalade"))); + gQueue.push(new synthEscapeKey("eml_marmalade", new focusChecker(textentry))); + + // no focus events for unfocused list controls when current item is + // changed. + gQueue.push(new synthFocus("emptylistbox")); + + gQueue.push(new changeCurrentItem("listbox", "lb_item1")); + gQueue.push(new changeCurrentItem("richlistbox", "rlb_item1")); +if (!MAC) { + gQueue.push(new changeCurrentItem("menulist", WIN ? "ml_marmalade" : "ml_tangerine")); +} + gQueue.push(new changeCurrentItem("emenulist", "eml_tangerine")); + + 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=433418" + title="Accessibles for focused HTML Select elements are not getting focused state"> + Mozilla Bug 433418 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=474893" + title="List controls should fire a focus event on the selected child when tabbing or when the selected child changes while the list is focused"> + Mozilla Bug 474893 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368" + title=" fire focus event on document accessible whenever the root or body element is focused"> + Mozilla Bug 552368 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <listbox id="listbox" rows="3"> + <listitem id="lb_item1" label="item1"/> + <listitem id="lb_item2" label="item1"/> + </listbox> + <listbox id="multisellistbox" rows="3" seltype="multiple"> + <listitem id="mslb_item1" label="item1"/> + <listitem id="mslb_item2" label="item1"/> + </listbox> + <listbox id="emptylistbox" rows="3"/> + <listbox id="mcolumnlistbox" rows="3"> + <listcols> + <listcol/> + <listcol/> + </listcols> + <listitem id="mclb_item1"> + <listcell label="George"/> + <listcell label="House Painter"/> + </listitem> + <listitem id="mclb_item2"> + <listcell label="Mary Ellen"/> + <listcell label="Candle Maker"/> + </listitem> + </listbox> + <listbox id="headerlistbox" rows="3"> + <listhead> + <listheader label="Name"/> + <listheader label="Occupation"/> + </listhead> + <listcols> + <listcol/> + <listcol flex="1"/> + </listcols> + <listitem id="hlb_item1"> + <listcell label="George"/> + <listcell label="House Painter"/> + </listitem> + <listitem id="hlb_item2"> + <listcell label="Mary Ellen"/> + <listcell label="Candle Maker"/> + </listitem> + </listbox> + + <richlistbox id="richlistbox"> + <richlistitem id="rlb_item1"> + <description>A XUL Description!</description> + </richlistitem> + <richlistitem id="rlb_item2"> + <button label="A XUL Button"/> + </richlistitem> + </richlistbox> + <richlistbox id="multiselrichlistbox" seltype="multiple"> + <richlistitem id="msrlb_item1"> + <description>A XUL Description!</description> + </richlistitem> + <richlistitem id="msrlb_item2"> + <button label="A XUL Button"/> + </richlistitem> + </richlistbox> + <richlistbox id="emptyrichlistbox" seltype="multiple"/> + + <menulist id="menulist"> + <menupopup> + <menuitem id="ml_tangerine" label="tangerine trees"/> + <menuitem id="ml_marmalade" label="marmalade skies"/> + </menupopup> + </menulist> + <menulist id="emenulist" editable="true"> + <menupopup> + <menuitem id="eml_tangerine" label="tangerine trees"/> + <menuitem id="eml_marmalade" label="marmalade skies"/> + </menupopup> + </menulist> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_menu.xul b/accessible/tests/mochitest/events/test_focus_menu.xul new file mode 100644 index 000000000..f205e8d25 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_menu.xul @@ -0,0 +1,119 @@ +<?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 focus testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTests() + { + // Test focus events. + gQueue = new eventQueue(); + + if (WIN) { + gQueue.push(new toggleTopMenu("fruit", new focusChecker("fruit"))); + gQueue.push(new synthRightKey("fruit", new focusChecker("vehicle"))); + gQueue.push(new synthEscapeKey("vehicle", new focusChecker(document))); + } + + // mouse move activate items but no focus event until menubar is active + gQueue.push(new synthMouseMove("fruit", new nofocusChecker("apple"))); + + // mouseover and click on menuitem makes it active before menubar is + // active + gQueue.push(new synthClick("fruit", new focusChecker("fruit"))); + + // mouseover on menuitem when menubar is active + gQueue.push(new synthMouseMove("apple", new focusChecker("apple"))); + + // keydown on disabled menuitem (disabled items are skipped on linux) + if (WIN) + gQueue.push(new synthDownKey("apple", new focusChecker("orange"))); + + // menu and menuitem are both active + // XXX: intermitent failure because two focus events may be coalesced, + // think to workaround or fix this issue, when done enable queue invoker + // below and remove next two. + //gQueue.push(new synthRightKey("apple", + // [ new focusChecker("vehicle"), + // new focusChecker("cycle")])); + gQueue.push(new synthClick("vehicle", new focusChecker("vehicle"))); + gQueue.push(new synthDownKey("cycle", new focusChecker("cycle"))); + + // open submenu + gQueue.push(new synthRightKey("cycle", new focusChecker("tricycle"))); + + // move to first menu in cycle, DOMMenuItemActive is fired for fruit, + // cycle and apple menuitems (bug 685191) + todo(false, "focus is fired for 'cycle' menuitem"); + //gQueue.push(new synthRightKey("vehicle", new focusChecker("apple"))); + + // click menuitem to close menu, focus gets back to document + gQueue.push(new synthClick("tricycle", new focusChecker(document))); + + //enableLogging("focus,DOMEvents,tree"); // logging for bug708927 + //gQueue.onFinish = function() { disableLogging(); } + + 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=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menubar> + <menu id="fruit" label="Fruit"> + <menupopup> + <menuitem id="apple" label="Apple"/> + <menuitem id="orange" label="Orange" disabled="true"/> + </menupopup> + </menu> + <menu id="vehicle" label="Vehicle"> + <menupopup> + <menu id="cycle" label="cycle"> + <menupopup> + <menuitem id="tricycle" label="tricycle"/> + </menupopup> + </menu> + <menuitem id="car" label="Car" disabled="true"/> + </menupopup> + </menu> + </menubar> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_name.html b/accessible/tests/mochitest/events/test_focus_name.html new file mode 100644 index 000000000..f0db26427 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_name.html @@ -0,0 +1,122 @@ +<html> + +<head> + <title>Accessible name testing on focus</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + /** + * Checker for invokers. + */ + function actionChecker(aID, aDescription) + { + this.__proto__ = new invokerChecker(EVENT_FOCUS, aID); + + this.check = function actionChecker_check(aEvent) + { + var target = aEvent.accessible; + is(target.description, aDescription, + "Wrong description for " + prettyName(target)); + } + } + + var gFocusHandler = { + handleEvent: function gFocusHandler_handleEvent(aEvent) { + var elm = aEvent.target; + if (elm.nodeType != nsIDOMNode.ELEMENT_NODE) + return; + + gTooltipElm.style.display = "block"; + + elm.setAttribute("aria-describedby", "tooltip"); + } + }; + + var gBlurHandler = { + handleEvent: function gBlurHandler_handleEvent(aEvent) { + gTooltipElm.style.display = "none"; + + var elm = aEvent.target; + if (elm.nodeType == nsIDOMNode.ELEMENT_NODE) + elm.removeAttribute("aria-describedby"); + } + }; + + /** + * Do tests. + */ + + // gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + var gButtonElm = null; + var gTextboxElm = null; + var gTooltipElm = null; + + function doTests() + { + gButtonElm = getNode("button"); + gTextboxElm = getNode("textbox"); + gTooltipElm = getNode("tooltip"); + + gButtonElm.addEventListener("focus", gFocusHandler, false); + gButtonElm.addEventListener("blur", gBlurHandler, false); + gTextboxElm.addEventListener("focus", gFocusHandler, false); + gTextboxElm.addEventListener("blur", gBlurHandler, false); + + // The aria-describedby is changed on DOM focus. Accessible description + // should be updated when a11y focus is fired. + gQueue = new eventQueue(nsIAccessibleEvent.EVENT_FOCUS); + gQueue.onFinish = function() + { + gButtonElm.removeEventListener("focus", gFocusHandler, false); + gButtonElm.removeEventListener("blur", gBlurHandler, false); + gTextboxElm.removeEventListener("focus", gFocusHandler, false); + gTextboxElm.removeEventListener("blur", gBlurHandler, false); + } + + var descr = "It's a tooltip"; + gQueue.push(new synthFocus("button", new actionChecker("button", descr))); + gQueue.push(new synthTab("textbox", new actionChecker("textbox", descr))); + + 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=520709" + title="mochitest to ensure name/description are updated on a11y focus if they were changed on DOM focus"> + Mozilla Bug 520709 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="tooltip" style="display: none" aria-hidden="true">It's a tooltip</div> + <button id="button">button</button> + <input id="textbox"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_selects.html b/accessible/tests/mochitest/events/test_focus_selects.html new file mode 100644 index 000000000..ef742c4b2 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_selects.html @@ -0,0 +1,118 @@ +<html> + +<head> + <title>Accessible focus testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTests() + { + // Bug 746534 - File causes crash or hang on OS X + if (MAC) { + todo(false, "Bug 746534 - test file causes crash or hang on OS X"); + SimpleTest.finish(); + return; + } + + gQueue = new eventQueue(); + + // first item is focused until there's selection + gQueue.push(new synthFocus("list", new focusChecker("orange"))); + + // item is selected and stays focused + gQueue.push(new synthDownKey("list", new nofocusChecker())); + + // last selected item is focused + gQueue.push(new synthDownKey("list", new focusChecker("apple"), { shiftKey: true })); + + // no focus event if nothing is changed + gQueue.push(new synthDownKey("list", new nofocusChecker("apple"))); + + // current item is focused + gQueue.push(new synthUpKey("list", new focusChecker("orange"), { ctrlKey: true })); + + // focus on empty list (no items to be focused) + gQueue.push(new synthTab("list", new focusChecker("emptylist"))); + + // current item is focused + gQueue.push(new synthShiftTab("emptylist", new focusChecker("orange"))); + + // collapsed combobox keeps a focus + gQueue.push(new synthFocus("combobox", new focusChecker("combobox"))); + gQueue.push(new synthDownKey("combobox", new nofocusChecker("combobox"))); + + // selected item is focused for expanded combobox + gQueue.push(new synthOpenComboboxKey("combobox", new focusChecker("cb_apple"))); + gQueue.push(new synthUpKey("combobox", new focusChecker("cb_orange"))); + + // collapsed combobx keeps a focus + gQueue.push(new synthEscapeKey("combobox", new focusChecker("combobox"))); + + // no focus events for unfocused list controls when current item is + // changed + gQueue.push(new synthFocus("emptylist")); + + gQueue.push(new changeCurrentItem("list", "orange")); + gQueue.push(new changeCurrentItem("combobox", "cb_apple")); + + 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=433418" + title="Accessibles for focused HTML Select elements are not getting focused state"> + Mozilla Bug 433418 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=474893" + title="List controls should fire a focus event on the selected child when tabbing or when the selected child changes while the list is focused"> + Mozilla Bug 474893 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="list" size="5" multiple=""> + <option id="orange">Orange</option> + <option id="apple">Apple</option> + </select> + + <select id="emptylist" size="5"></select> + + <select id="combobox"> + <option id="cb_orange">Orange</option> + <option id="cb_apple">Apple</option> + </select> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_tabbox.xul b/accessible/tests/mochitest/events/test_focus_tabbox.xul new file mode 100644 index 000000000..c51546405 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_tabbox.xul @@ -0,0 +1,103 @@ +<?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="Tabbox focus testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTests() + { + if (MAC) { + todo(false, "Tests disabled because of imminent failure."); + SimpleTest.finish(); + return; + } + + // Test focus events. + gQueue = new eventQueue(); + + var textbox = getNode("textbox").inputField; + gQueue.push(new synthClick("tab1", new focusChecker("tab1"))); + gQueue.push(new synthTab("tab1", new focusChecker("checkbox1"))); + gQueue.push(new synthKey("tab1", "VK_TAB", { ctrlKey: true }, + new focusChecker(textbox))); + gQueue.push(new synthKey("tab2", "VK_TAB", { ctrlKey: true }, + new focusChecker("tab3"))); + gQueue.push(new synthKey("tab3", "VK_TAB", { ctrlKey: true }, + new focusChecker("tab1"))); + + 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=370396" + title="Control+Tab to an empty tab panel in a tabbox causes focus to leave the tabbox"> + Mozilla Bug 370396 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tabbox> + <tabs> + <tab id="tab1" label="Tab1" selected="true"/> + <tab id="tab2" label="Tab2" /> + <tab id="tab3" label="Tab3" /> + </tabs> + <tabpanels> + <tabpanel orient="vertical"> + <groupbox orient="vertical"> + <checkbox id="checkbox1" label="Monday" width="75"/> + <checkbox label="Tuesday" width="75"/> + <checkbox label="Wednesday" width="75"/> + <checkbox label="Thursday" width="75"/> + <checkbox label="Friday" width="75"/> + <checkbox label="Saturday" width="75"/> + <checkbox label="Sunday" width="75"/> + </groupbox> + + <spacer style="height: 10px" /> + <label value="Label After checkboxes" /> + </tabpanel> + <tabpanel orient="vertical"> + <textbox id="textbox" /> + </tabpanel> + <tabpanel orient="vertical"> + <description>Tab 3 content</description> + </tabpanel> + </tabpanels> + </tabbox> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_tree.xul b/accessible/tests/mochitest/events/test_focus_tree.xul new file mode 100644 index 000000000..995b08e25 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_tree.xul @@ -0,0 +1,122 @@ +<?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="XUL tree focus testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.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[ + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function focusTree(aTreeID) + { + var checker = new focusChecker(getFirstTreeItem, aTreeID); + this.__proto__ = new synthFocus(aTreeID, [ checker ]); + } + + function moveToNextItem(aTreeID) + { + var checker = new focusChecker(getSecondTreeItem, aTreeID); + this.__proto__ = new synthDownKey(aTreeID, [ checker ]); + } + + //////////////////////////////////////////////////////////////////////////// + // Helpers + + function getTreeItemAt(aTreeID, aIdx) + { return getAccessible(aTreeID).getChildAt(aIdx + 1); } + + function getFirstTreeItem(aTreeID) + { return getTreeItemAt(aTreeID, 0); } + + function getSecondTreeItem(aTreeID) + { return getTreeItemAt(aTreeID, 1); } + + //////////////////////////////////////////////////////////////////////////// + // Test + + var gQueue = null; + + //gA11yEventDumpID = "debug"; // debugging + //gA11yEventDumpToConsole = true; // debugging + + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new focusTree("tree")); + gQueue.push(new moveToNextItem("tree")); + gQueue.push(new synthFocus("emptytree")); + + // no focus event for changed current item for unfocused tree + gQueue.push(new changeCurrentItem("tree", 0)); + + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(5)); + ]]> + </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=386821" + title="Need better solution for firing delayed event against xul tree"> + Mozilla Bug 386821 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=406308" + title="Don't fire accessible focus events if widget is not actually in focus, confuses screen readers"> + Mozilla Bug 406308 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="debug"/> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col1" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="column 2"/> + </treecols> + <treechildren id="treechildren"/> + </tree> + <tree id="emptytree" flex="1"> + <treecols> + <treecol id="emptytree_col1" flex="1" primary="true" label="column"/> + <treecol id="emptytree_col2" flex="1" label="column 2"/> + </treecols> + <treechildren id="emptytree_treechildren"/> + </tree> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/events/test_fromUserInput.html b/accessible/tests/mochitest/events/test_fromUserInput.html new file mode 100644 index 000000000..1cfeedf0b --- /dev/null +++ b/accessible/tests/mochitest/events/test_fromUserInput.html @@ -0,0 +1,127 @@ +<html> + +<head> + <title>Testing of isFromUserInput in text events</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + /** + * Remove text data from HTML input. + */ + function removeTextFromInput(aID, aStart, aEnd, aText, aFromUser) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new textChangeChecker(aID, aStart, aEnd, aText, false, aFromUser) + ]; + + this.invoke = function removeTextFromInput_invoke() + { + const nsIDOMNSEditableElement = + Components.interfaces.nsIDOMNSEditableElement; + + this.DOMNode.focus(); + this.DOMNode.setSelectionRange(aStart, aEnd); + + synthesizeKey("VK_DELETE", {}); + } + + this.getID = function removeTextFromInput_getID() + { + return "Remove text from " + aStart + " to " + aEnd + " for " + + prettyName(aID); + } + } + + /** + * Remove text data from text node. + */ + function removeTextFromContentEditable(aID, aStart, aEnd, aText, aFromUser) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new textChangeChecker(aID, aStart, aEnd, aText, false, aFromUser) + ]; + + this.invoke = function removeTextFromContentEditable_invoke() + { + const nsIDOMNSEditableElement = + Components.interfaces.nsIDOMNSEditableElement; + + this.DOMNode.focus(); + this.textNode = getNode(aID).firstChild; + var selection = window.getSelection(); + var range = document.createRange(); + range.setStart(this.textNode, aStart); + range.setEnd(this.textNode, aEnd); + selection.addRange(range); + + synthesizeKey("VK_DELETE", {}); + } + + this.getID = function removeTextFromContentEditable_getID() + { + return "Remove text from " + aStart + " to " + aEnd + " for " + + prettyName(aID); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + // gA11yEventDumpID = "eventdump"; // debug stuff + + var gQueue = null; + + function doTests() + { + gQueue = new eventQueue(); + + // Focused editable text node + gQueue.push(new removeTextFromContentEditable("div", 0, 3, "hel", true)); + + // Focused editable HTML input + gQueue.push(new removeTextFromInput("input", 1, 2, "n", true)); + + 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=686909" + title="isFromUserInput flag on accessible text change events not correct"> + Mozilla Bug 686909 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + <div id="eventdump"></div> + + <div id="div" contentEditable="true">hello</div> + <input id="input" value="input"> + +</body> + +</html> diff --git a/accessible/tests/mochitest/events/test_label.xul b/accessible/tests/mochitest/events/test_label.xul new file mode 100644 index 000000000..e82763a7c --- /dev/null +++ b/accessible/tests/mochitest/events/test_label.xul @@ -0,0 +1,177 @@ +<?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="Tests: accessible XUL label/description events"> + + <script type="application/javascript" + 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 + + const kRecreated = 0; + const kTextRemoved = 1; + const kTextChanged = 2; + + const kNoValue = 0; + + /** + * Set/remove @value attribute. + */ + function setValue(aID, aValue, aResult, aOldValue) + { + this.labelNode = getNode(aID); + + this.eventSeq = []; + + switch (aResult) { + case kRecreated: + this.eventSeq.push(new invokerChecker(EVENT_HIDE, this.labelNode)); + this.eventSeq.push(new invokerChecker(EVENT_SHOW, this.labelNode)); + break; + case kTextRemoved: + this.eventSeq.push( + new textChangeChecker(this.labelNode, 0, aOldValue.length, + aOldValue, false)); + break; + case kTextChanged: + this.eventSeq.push( + new textChangeChecker(this.labelNode, 0, aOldValue.length, + aOldValue, false)); + this.eventSeq.push( + new textChangeChecker(this.labelNode, 0, aValue.length, + aValue, true)); + break; + } + + this.invoke = function setValue_invoke() + { + if (aValue === kNoValue) + this.labelNode.removeAttribute("value"); + else + this.labelNode.setAttribute("value", aValue); + } + + this.finalCheck = function setValue_finalCheck() + { + var tree = + { LABEL: [ + { TEXT_LEAF: [ ] } + ] }; + testAccessibleTree(aID, tree); + } + + this.getID = function setValue_getID() + { + return "set @value='" + aValue + "' for label " + prettyName(aID); + } + } + + /** + * Change @crop attribute. + */ + function setCrop(aID, aCropValue, aRemovedText, aInsertedText) + { + this.labelNode = getNode(aID); + this.width = this.labelNode.boxObject.width; + this.charWidth = this.width / this.labelNode.value.length; + + this.eventSeq = [ + new textChangeChecker(this.labelNode, 0, -1, aRemovedText, false), + new textChangeChecker(this.labelNode, 0, -1, aInsertedText, true) + ]; + + this.invoke = function setCrop_invoke() + { + if (!this.labelNode.hasAttribute("crop")) + this.labelNode.width = Math.floor(this.width - 2 * this.charWidth); + + this.labelNode.setAttribute("crop", aCropValue); + } + + this.getID = function setCrop_finalCheck() + { + return "set crop " + aCropValue; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new setValue("label", "shiroka strana", kRecreated)); + gQueue.push(new setValue("label", "?<>!+_", kTextChanged, "shiroka strana")); + gQueue.push(new setValue("label", "", kTextRemoved, "?<>!+_")); + gQueue.push(new setValue("label", kNoValue, kRecreated)); + + gQueue.push(new setValue("descr", "hello world", kRecreated)); + gQueue.push(new setValue("descr", "si_ya", kTextChanged, "hello world")); + gQueue.push(new setValue("descr", "", kTextRemoved, "si_ya")); + gQueue.push(new setValue("descr", kNoValue, kRecreated)); + + if (MAC) { + // "valuetocro" -> "…etocro" + gQueue.push(new setCrop("croplabel", "left", "valu", "…")); + // "…etocro", "val…cro" + gQueue.push(new setCrop("croplabel", "center", "…eto", "val…")); + } else { + // "valuetocro" -> "…uetocro" + gQueue.push(new setCrop("croplabel", "left", "val", "…")); + // "…uetocro" -> "valu…cro" + gQueue.push(new setCrop("croplabel", "center", "…ueto", "valu…")); + } + + 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=396166" + title="xul:label@value accessible should implement nsIAccessibleText"> + Bug 396166 + </a> + <br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <label id="label">hello</label> + <description id="descr">hello</description> + + <hbox> + <label id="croplabel" value="valuetocro" + style="font-family: monospace;"/> + </hbox> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/events/test_menu.xul b/accessible/tests/mochitest/events/test_menu.xul new file mode 100644 index 000000000..bae36fb71 --- /dev/null +++ b/accessible/tests/mochitest/events/test_menu.xul @@ -0,0 +1,202 @@ +<?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 menu events testing for XUL menu"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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"> + function openFileMenu() + { + this.eventSeq = [ + new invokerChecker(EVENT_MENU_START, getNode("menubar")), + new invokerChecker(EVENT_MENUPOPUP_START, getNode("menupopup-file")) + // new invokerChecker(EVENT_FOCUS, getNode("menuitem-newtab")) intermitent failure + ]; + + this.invoke = function openFileMenu_invoke() + { + synthesizeKey("F", { altKey: true, shiftKey: true }); + } + + this.getID = function openFileMenu_getID() + { + return "open file menu by alt+F press"; + } + } + + function openEditMenu() + { + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_END, getNode("menupopup-file")), + new invokerChecker(EVENT_MENUPOPUP_START, getNode("menupopup-edit")) + // new invokerChecker(EVENT_FOCUS, getNode("menuitem-undo")) intermitent failure + ]; + + this.invoke = function openEditMenu_invoke() + { + synthesizeKey("VK_RIGHT", { }); + } + + this.getID = function openEditMenu_getID() + { + return "open edit menu by lef arrow press"; + } + } + + function closeEditMenu() + { + this.eventSeq = [ + //new invokerChecker(EVENT_FOCUS, document), intermitent failure + new invokerChecker(EVENT_MENUPOPUP_END, getNode("menupopup-edit")), + new invokerChecker(EVENT_MENU_END, getNode("menubar")) + ]; + + this.invoke = function closeEditMenu_invoke() + { + synthesizeKey("VK_ESCAPE", { }); + } + + this.getID = function closeEditMenu_getID() + { + return "close edit menu, leave menubar"; + } + } + + function focusFileMenu() + { + this.eventSeq = [ + new invokerChecker(EVENT_MENU_START, getNode("menubar")) + // new invokerChecker(EVENT_FOCUS, getNode("menuitem-file")) //intermitent failure + ]; + + this.invoke = function focusFileMenu_invoke() + { + synthesizeKey("VK_ALT", { }); + } + + this.getID = function focusFileMenu_getID() + { + return "activate menubar, focus file menu (atl press)"; + } + } + + function focusEditMenu() + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getNode("menuitem-edit")) + ]; + + this.invoke = function focusEditMenu_invoke() + { + synthesizeKey("VK_RIGHT", { }); + } + + this.getID = function focusEditMenu_getID() + { + return "focus edit menu"; + } + } + + function leaveMenubar() + { + this.eventSeq = [ + //new invokerChecker(EVENT_FOCUS, document), intermitent failure + new invokerChecker(EVENT_MENU_END, getNode("menubar")) + ]; + + this.invoke = function leaveMenubar_invoke() + { + synthesizeKey("VK_ESCAPE", { }); + } + + this.getID = function leaveMenubar_getID() + { + return "leave menubar"; + } + } + + /** + * Do tests. + */ + + //gA11yEventDumpID = "eventdump"; + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTests() + { + if (!WIN) { + todo(false, "Enable this test on other platforms."); + SimpleTest.finish(); + return; + } + + todo(false, + "Fix intermitent failures. Focus may randomly occur before or after menupopup events!"); + + gQueue = new eventQueue(); + + gQueue.push(new openFileMenu()); + gQueue.push(new openEditMenu()); + gQueue.push(new closeEditMenu()); + + // Alt key is used to active menubar and focus menu item on Windows, + // other platforms requires setting a ui.key.menuAccessKeyFocuses + // preference. + if (WIN || LINUX) { + gQueue.push(new focusFileMenu()); + gQueue.push(new focusEditMenu()); + gQueue.push(new leaveMenubar()); + } + + 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=615189" + title="Clean up FireAccessibleFocusEvent"> + Mozilla Bug 615189 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menubar id="menubar"> + <menu id="menuitem-file" label="File" accesskey="F"> + <menupopup id="menupopup-file"> + <menuitem id="menuitem-newtab" label="New Tab"/> + </menupopup> + </menu> + <menu id="menuitem-edit" label="Edit" accesskey="E"> + <menupopup id="menupopup-edit"> + <menuitem id="menuitem-undo" label="Undo"/> + </menupopup> + </menu> + </menubar> + + <vbox id="eventdump" role="log"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_mutation.html b/accessible/tests/mochitest/events/test_mutation.html new file mode 100644 index 000000000..232a09727 --- /dev/null +++ b/accessible/tests/mochitest/events/test_mutation.html @@ -0,0 +1,632 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + div.displayNone a { display:none; } + div.visibilityHidden a { visibility:hidden; } +</style> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + /** + * Invokers. + */ + var kNoEvents = 0; + + var kShowEvent = 1; + var kHideEvent = 2; + var kReorderEvent = 4; + var kShowEvents = kShowEvent | kReorderEvent; + var kHideEvents = kHideEvent | kReorderEvent; + var kHideAndShowEvents = kHideEvents | kShowEvent; + + /** + * Base class to test mutation a11y events. + * + * @param aNodeOrID [in] node invoker's action is executed for + * @param aEventTypes [in] events to register (see constants above) + * @param aDoNotExpectEvents [in] boolean indicates if events are expected + */ + function mutateA11yTree(aNodeOrID, aEventTypes, aDoNotExpectEvents) + { + // Interface + this.DOMNode = getNode(aNodeOrID); + this.doNotExpectEvents = aDoNotExpectEvents; + this.eventSeq = []; + this.unexpectedEventSeq = []; + + /** + * Change default target (aNodeOrID) registered for the given event type. + */ + this.setTarget = function mutateA11yTree_setTarget(aEventType, aTarget) + { + var type = this.getA11yEventType(aEventType); + for (var idx = 0; idx < this.getEventSeq().length; idx++) { + if (this.getEventSeq()[idx].type == type) { + this.getEventSeq()[idx].target = aTarget; + return idx; + } + } + return -1; + } + + /** + * Replace the default target currently registered for a given event type + * with the nodes in the targets array. + */ + this.setTargets = function mutateA11yTree_setTargets(aEventType, aTargets) { + var targetIdx = this.setTarget(aEventType, aTargets[0]); + + var type = this.getA11yEventType(aEventType); + for (var i = 1; i < aTargets.length; i++) { + var checker = new invokerChecker(type, aTargets[i]); + this.getEventSeq().splice(++targetIdx, 0, checker); + } + } + + // Implementation + this.getA11yEventType = function mutateA11yTree_getA11yEventType(aEventType) + { + if (aEventType == kReorderEvent) + return nsIAccessibleEvent.EVENT_REORDER; + + if (aEventType == kHideEvent) + return nsIAccessibleEvent.EVENT_HIDE; + + if (aEventType == kShowEvent) + return nsIAccessibleEvent.EVENT_SHOW; + } + + this.getEventSeq = function mutateA11yTree_getEventSeq() + { + return this.doNotExpectEvents ? this.unexpectedEventSeq : this.eventSeq; + } + + if (aEventTypes & kHideEvent) { + var checker = new invokerChecker(this.getA11yEventType(kHideEvent), + this.DOMNode); + this.getEventSeq().push(checker); + } + + if (aEventTypes & kShowEvent) { + var checker = new invokerChecker(this.getA11yEventType(kShowEvent), + this.DOMNode); + this.getEventSeq().push(checker); + } + + if (aEventTypes & kReorderEvent) { + var checker = new invokerChecker(this.getA11yEventType(kReorderEvent), + this.DOMNode.parentNode); + this.getEventSeq().push(checker); + } + } + + /** + * Change CSS style for the given node. + */ + function changeStyle(aNodeOrID, aProp, aValue, aEventTypes) + { + this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false); + + this.invoke = function changeStyle_invoke() + { + this.DOMNode.style[aProp] = aValue; + } + + this.getID = function changeStyle_getID() + { + return aNodeOrID + " change style " + aProp + " on value " + aValue; + } + } + + /** + * Change class name for the given node. + */ + function changeClass(aParentNodeOrID, aNodeOrID, aClassName, aEventTypes) + { + this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false); + + this.invoke = function changeClass_invoke() + { + this.parentDOMNode.className = aClassName; + } + + this.getID = function changeClass_getID() + { + return aNodeOrID + " change class " + aClassName; + } + + this.parentDOMNode = getNode(aParentNodeOrID); + } + + /** + * Clone the node and append it to its parent. + */ + function cloneAndAppendToDOM(aNodeOrID, aEventTypes, + aTargetsFunc, aReorderTargetFunc) + { + var eventTypes = aEventTypes || kShowEvents; + var doNotExpectEvents = (aEventTypes == kNoEvents); + + this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes, + doNotExpectEvents); + + this.invoke = function cloneAndAppendToDOM_invoke() + { + var newElm = this.DOMNode.cloneNode(true); + newElm.removeAttribute('id'); + + var targets = aTargetsFunc ? + aTargetsFunc.call(null, newElm) : [newElm]; + this.setTargets(kShowEvent, targets); + + if (aReorderTargetFunc) { + var reorderTarget = aReorderTargetFunc.call(null, this.DOMNode); + this.setTarget(kReorderEvent, reorderTarget); + } + + this.DOMNode.parentNode.appendChild(newElm); + } + + this.getID = function cloneAndAppendToDOM_getID() + { + return aNodeOrID + " clone and append to DOM."; + } + } + + /** + * Removes the node from DOM. + */ + function removeFromDOM(aNodeOrID, aEventTypes, + aTargetsFunc, aReorderTargetFunc) + { + var eventTypes = aEventTypes || kHideEvents; + var doNotExpectEvents = (aEventTypes == kNoEvents); + + this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes, + doNotExpectEvents); + + this.invoke = function removeFromDOM_invoke() + { + this.DOMNode.parentNode.removeChild(this.DOMNode); + } + + this.getID = function removeFromDOM_getID() + { + return prettyName(aNodeOrID) + " remove from DOM."; + } + + if (aTargetsFunc && (eventTypes & kHideEvent)) + this.setTargets(kHideEvent, aTargetsFunc.call(null, this.DOMNode)); + + if (aReorderTargetFunc && (eventTypes & kReorderEvent)) + this.setTarget(kReorderEvent, + aReorderTargetFunc.call(null, this.DOMNode)); + } + + /** + * Clone the node and replace the original node by cloned one. + */ + function cloneAndReplaceInDOM(aNodeOrID) + { + this.__proto__ = new mutateA11yTree(aNodeOrID, kHideAndShowEvents, + false); + + this.invoke = function cloneAndReplaceInDOM_invoke() + { + this.DOMNode.parentNode.replaceChild(this.newElm, this.DOMNode); + } + + this.getID = function cloneAndReplaceInDOM_getID() + { + return aNodeOrID + " clone and replace in DOM."; + } + + this.newElm = this.DOMNode.cloneNode(true); + this.newElm.removeAttribute('id'); + this.setTarget(kShowEvent, this.newElm); + } + + /** + * Trigger content insertion (flush layout), removal and insertion of + * the same element for the same parent. + */ + function test1(aContainerID) + { + this.divNode = document.createElement("div"); + this.divNode.setAttribute("id", "div-test1"); + this.containerNode = getNode(aContainerID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.divNode), + new invokerChecker(EVENT_REORDER, this.containerNode) + ]; + + this.invoke = function test1_invoke() + { + this.containerNode.appendChild(this.divNode); + getComputedStyle(this.divNode, "").color; + this.containerNode.removeChild(this.divNode); + this.containerNode.appendChild(this.divNode); + } + + this.getID = function test1_getID() + { + return "fuzzy test #1: content insertion (flush layout), removal and" + + "reinsertion"; + } + } + + /** + * Trigger content insertion (flush layout), removal and insertion of + * the same element for the different parents. + */ + function test2(aContainerID, aTmpContainerID) + { + this.divNode = document.createElement("div"); + this.divNode.setAttribute("id", "div-test2"); + this.containerNode = getNode(aContainerID); + this.tmpContainerNode = getNode(aTmpContainerID); + this.container = getAccessible(this.containerNode); + this.tmpContainer = getAccessible(this.tmpContainerNode); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.divNode), + new invokerChecker(EVENT_REORDER, this.containerNode) + ]; + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_REORDER, this.tmpContainerNode) + ]; + + this.invoke = function test2_invoke() + { + this.tmpContainerNode.appendChild(this.divNode); + getComputedStyle(this.divNode, "").color; + this.tmpContainerNode.removeChild(this.divNode); + this.containerNode.appendChild(this.divNode); + } + + this.getID = function test2_getID() + { + return "fuzzy test #2: content insertion (flush layout), removal and" + + "reinsertion under another container"; + } + } + + /** + * Content insertion (flush layout) and then removal (nothing was changed). + */ + function test3(aContainerID) + { + this.divNode = document.createElement("div"); + this.divNode.setAttribute("id", "div-test3"); + this.containerNode = getNode(aContainerID); + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_SHOW, this.divNode), + new invokerChecker(EVENT_HIDE, this.divNode), + new invokerChecker(EVENT_REORDER, this.containerNode) + ]; + + this.invoke = function test3_invoke() + { + this.containerNode.appendChild(this.divNode); + getComputedStyle(this.divNode, "").color; + this.containerNode.removeChild(this.divNode); + } + + this.getID = function test3_getID() + { + return "fuzzy test #3: content insertion (flush layout) and removal"; + } + } + + function insertReferredElm(aContainerID) + { + this.containerNode = getNode(aContainerID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.firstChild; }, this.containerNode), + new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode), + new invokerChecker(EVENT_REORDER, this.containerNode) + ]; + + this.invoke = function insertReferredElm_invoke() + { + this.containerNode.innerHTML = + "<span id='insertReferredElms_span'></span><input aria-labelledby='insertReferredElms_span'>"; + } + + this.getID = function insertReferredElm_getID() + { + return "insert inaccessible element and then insert referring element to make it accessible"; + } + } + + function showHiddenParentOfVisibleChild() + { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("c4_child")), + new invokerChecker(EVENT_SHOW, getNode("c4_middle")), + new invokerChecker(EVENT_REORDER, getNode("c4")) + ]; + + this.invoke = function showHiddenParentOfVisibleChild_invoke() + { + getNode("c4_middle").style.visibility = 'visible'; + } + + this.getID = function showHiddenParentOfVisibleChild_getID() + { + return "show hidden parent of visible child"; + } + } + + function hideNDestroyDoc() + { + this.txt = null; + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, function() { return this.txt; }.bind(this)) + ]; + + this.invoke = function hideNDestroyDoc_invoke() + { + this.txt = getAccessible('c5').firstChild.firstChild; + this.txt.DOMNode.parentNode.removeChild(this.txt.DOMNode); + } + + this.check = function hideNDestroyDoc_check() + { + getNode('c5').parentNode.removeChild(getNode('c5')); + } + + this.getID = function hideNDestroyDoc_getID() + { + return "remove text node and destroy a document on hide event"; + } + } + + function hideHideNDestroyDoc() + { + this.target = null; + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, function() { return this.target; }.bind(this)) + ]; + + this.invoke = function hideHideNDestroyDoc_invoke() + { + var doc = getAccessible('c6').firstChild; + var l1 = doc.firstChild; + this.target = l1.firstChild; + var l2 = doc.lastChild; + l1.DOMNode.removeChild(l1.DOMNode.firstChild); + l2.DOMNode.removeChild(l2.DOMNode.firstChild); + } + + this.check = function hideHideNDestroyDoc_check() + { + getNode('c6').parentNode.removeChild(getNode('c6')); + } + + this.getID = function hideHideNDestroyDoc_getID() + { + return "remove text nodes (2 events in the queue) and destroy a document on first hide event"; + } + } + + /** + * Target getters. + */ + function getFirstChild(aNode) + { + return [aNode.firstChild]; + } + function getLastChild(aNode) + { + return [aNode.lastChild]; + } + + function getNEnsureFirstChild(aNode) + { + var node = aNode.firstChild; + getAccessible(node); + return [node]; + } + + function getNEnsureChildren(aNode) + { + var children = []; + var node = aNode.firstChild; + do { + children.push(node); + getAccessible(node); + node = node.nextSibling; + } while (node); + + return children; + } + + function getParent(aNode) + { + return aNode.parentNode; + } + + //gA11yEventDumpToConsole = true; // debug stuff + //enableLogging("events,verbose"); + + /** + * Do tests. + */ + var gQueue = null; + + function doTests() + { + gQueue = new eventQueue(); + + // Show/hide events by changing of display style of accessible DOM node + // from 'inline' to 'none', 'none' to 'inline'. + var id = "link1"; + getAccessible(id); // ensure accessible is created + gQueue.push(new changeStyle(id, "display", "none", kHideEvents)); + gQueue.push(new changeStyle(id, "display", "inline", kShowEvents)); + + // Show/hide events by changing of visibility style of accessible DOM node + // from 'visible' to 'hidden', 'hidden' to 'visible'. + var id = "link2"; + getAccessible(id); + gQueue.push(new changeStyle(id, "visibility", "hidden", kHideEvents)); + gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents)); + + // Show/hide events by changing of display style of accessible DOM node + // from 'inline' to 'block', 'block' to 'inline'. + var id = "link3"; + getAccessible(id); // ensure accessible is created + gQueue.push(new changeStyle(id, "display", "block", kHideAndShowEvents)); + gQueue.push(new changeStyle(id, "display", "inline", kHideAndShowEvents)); + + // Show/hide events by changing of visibility style of accessible DOM node + // from 'collapse' to 'visible', 'visible' to 'collapse'. + var id = "link4"; + gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents)); + gQueue.push(new changeStyle(id, "visibility", "collapse", kHideEvents)); + + // Show/hide events by adding new accessible DOM node and removing old one. + var id = "link5"; + gQueue.push(new cloneAndAppendToDOM(id)); + gQueue.push(new removeFromDOM(id)); + + // No show/hide events by adding new not accessible DOM node and removing + // old one, no reorder event for their parent. + var id = "child1"; + gQueue.push(new cloneAndAppendToDOM(id, kNoEvents)); + gQueue.push(new removeFromDOM(id, kNoEvents)); + + // Show/hide events by adding new accessible DOM node and removing + // old one, there is reorder event for their parent. + var id = "child2"; + gQueue.push(new cloneAndAppendToDOM(id)); + gQueue.push(new removeFromDOM(id)); + + // Show/hide events by adding new DOM node containing accessible DOM and + // removing old one, there is reorder event for their parent. + var id = "child3"; + gQueue.push(new cloneAndAppendToDOM(id, kShowEvents, getFirstChild, + getParent)); + + // Hide event for accessible child of unaccessible removed DOM node and + // reorder event for its parent. + gQueue.push(new removeFromDOM(id, kHideEvents, + getNEnsureFirstChild, getParent)); + + // Hide events for accessible children of unaccessible removed DOM node + // and reorder event for its parent. + gQueue.push(new removeFromDOM("child4", kHideEvents, + getNEnsureChildren, getParent)); + + // Show/hide events by creating new accessible DOM node and replacing + // old one. + getAccessible("link6"); // ensure accessible is created + gQueue.push(new cloneAndReplaceInDOM("link6")); + + // Show/hide events by changing class name on the parent node. + gQueue.push(new changeClass("container2", "link7", "", kShowEvents)); + gQueue.push(new changeClass("container2", "link7", "displayNone", + kHideEvents)); + + gQueue.push(new changeClass("container3", "link8", "", kShowEvents)); + gQueue.push(new changeClass("container3", "link8", "visibilityHidden", + kHideEvents)); + + gQueue.push(new test1("testContainer")); + gQueue.push(new test2("testContainer", "testContainer2")); + gQueue.push(new test2("testContainer", "testNestedContainer")); + gQueue.push(new test3("testContainer")); + gQueue.push(new insertReferredElm("testContainer3")); + gQueue.push(new showHiddenParentOfVisibleChild()); + + gQueue.push(new hideNDestroyDoc()); + gQueue.push(new hideHideNDestroyDoc()); + 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=469985" + title=" turn the test from bug 354745 into mochitest"> + Mozilla Bug 469985</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=472662" + title="no reorder event when html:link display property is changed from 'none' to 'inline'"> + Mozilla Bug 472662</a> + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275</a> + <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> + <a target="_blank" + title="Update accessible tree on content insertion after layout" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=498015"> + Mozilla Bug 498015</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div id="testContainer"> + <a id="link1" href="http://www.google.com">Link #1</a> + <a id="link2" href="http://www.google.com">Link #2</a> + <a id="link3" href="http://www.google.com">Link #3</a> + <a id="link4" href="http://www.google.com" style="visibility:collapse">Link #4</a> + <a id="link5" href="http://www.google.com">Link #5</a> + + <div id="container" role="list"> + <span id="child1"></span> + <span id="child2" role="listitem"></span> + <span id="child3"><span role="listitem"></span></span> + <span id="child4"><span id="child4_1" role="listitem"></span><span id="child4_2" role="listitem"></span></span> + </div> + + <a id="link6" href="http://www.google.com">Link #6</a> + + <div id="container2" class="displayNone"><a id="link7">Link #7</a></div> + <div id="container3" class="visibilityHidden"><a id="link8">Link #8</a></div> + <div id="testNestedContainer"></div> + </div> + <div id="testContainer2"></div> + <div id="testContainer3"></div> + + <div id="c4"> + <div style="visibility:hidden" id="c4_middle"> + <div style="visibility:visible" id="c4_child"></div> + </div> + + <iframe id="c5" src="data:text/html,hey"></iframe> + <iframe id="c6" src="data:text/html,<label>l</label><label>l</label>"></iframe> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_mutation.xhtml b/accessible/tests/mochitest/events/test_mutation.xhtml new file mode 100644 index 000000000..e1aabe612 --- /dev/null +++ b/accessible/tests/mochitest/events/test_mutation.xhtml @@ -0,0 +1,97 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <bindings xmlns="http://www.mozilla.org/xbl" > + <binding id="button"> + <content> + <button xmlns="http://www.w3.org/1999/xhtml">a button</button> + </content> + </binding> + </bindings> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + /** + * Insert a not accessible bound element containing an accessible element + * in anonymous content. + */ + function insertBinding(aContainerID) + { + this.containerNode = getNode(aContainerID); + + function getButtonFromBinding(aNode) + { + try { return document.getAnonymousNodes(aNode.firstChild)[0]; } + catch (e) { return null; } + } + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getButtonFromBinding, this.containerNode), + new invokerChecker(EVENT_REORDER, this.containerNode) + ]; + + this.invoke = function insertBinding_invoke() + { + var span = document.createElement("span"); + span.setAttribute("style", "-moz-binding:url(#button)"); + this.containerNode.appendChild(span); + } + + this.getID = function insertBinding_getID() + { + return "insert button binding"; + } + } + + /** + * Do tests. + */ + var gQueue = null; + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new insertBinding("testContainer")); + + 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=646369" + title="UpdateTree should rely on accessible tree walker rather than DOM tree traversal"> + Mozilla Bug 646369</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div id="testContainer"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_namechange.html b/accessible/tests/mochitest/events/test_namechange.html new file mode 100644 index 000000000..935d865e9 --- /dev/null +++ b/accessible/tests/mochitest/events/test_namechange.html @@ -0,0 +1,123 @@ +<html> + +<head> + <title>Accessible name change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function setAttr(aID, aAttr, aValue, aChecker) + { + this.eventSeq = [ aChecker ]; + this.invoke = function setAttr_invoke() + { + getNode(aID).setAttribute(aAttr, aValue); + } + + this.getID = function setAttr_getID() + { + return "set attr '" + aAttr + "', value '" + aValue + "'"; + } + } + + /** + * No name change on an accessible, because the accessible is recreated. + */ + function setAttr_recreate(aID, aAttr, aValue) + { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getAccessible(aID)), + new invokerChecker(EVENT_SHOW, aID) + ]; + this.invoke = function setAttr_recreate_invoke() + { + todo(false, "No accessible recreation should happen, just name change event"); + getNode(aID).setAttribute(aAttr, aValue); + } + + this.getID = function setAttr_recreate_getID() + { + return "set attr '" + aAttr + "', value '" + aValue + "'"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + //gA11yEventDumpToConsole = true; // debuggin + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new setAttr("tst1", "aria-label", "hi", + new invokerChecker(EVENT_NAME_CHANGE, "tst1"))); + gQueue.push(new setAttr("tst1", "aria-labelledby", "display", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1"))); + gQueue.push(new setAttr("tst1", "alt", "alt", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1"))); + gQueue.push(new setAttr("tst1", "title", "title", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1"))); + + gQueue.push(new setAttr("tst2", "aria-labelledby", "display", + new invokerChecker(EVENT_NAME_CHANGE, "tst2"))); + gQueue.push(new setAttr("tst2", "alt", "alt", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2"))); + gQueue.push(new setAttr("tst2", "title", "title", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2"))); + + gQueue.push(new setAttr_recreate("tst3", "alt", "alt")); + gQueue.push(new setAttr("tst3", "title", "title", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst3"))); + + gQueue.push(new setAttr("tst4", "title", "title", + new invokerChecker(EVENT_NAME_CHANGE, "tst4"))); + + 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=991969" + title="Event not fired when description changes"> + Bug 991969 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <img id="tst1"> + <img id="tst2"> + <img id="tst3"> + <img id="tst4"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_namechange.xul b/accessible/tests/mochitest/events/test_namechange.xul new file mode 100644 index 000000000..9d688585c --- /dev/null +++ b/accessible/tests/mochitest/events/test_namechange.xul @@ -0,0 +1,92 @@ +<?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"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/chrome-harness.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + + /** + * Check name changed a11y event. + */ + function nameChangeChecker(aMsg, aID) + { + this.type = EVENT_NAME_CHANGE; + + function targetGetter() + { + return getAccessible(aID); + } + Object.defineProperty(this, "target", { get: targetGetter }); + + this.getID = function getID() + { + return aMsg + " name changed"; + } + } + + function changeRichListItemChild() + { + this.invoke = function changeRichListItemChild_invoke() + { + getNode('childcontent').setAttribute('value', 'Changed.'); + } + + this.eventSeq = + [ + new nameChangeChecker("changeRichListItemChild: ", "listitem") + ]; + + this.getID = function changeRichListItemChild_getID() + { + return "changeRichListItemChild"; + } + } + + function doTest() + { + var queue = new eventQueue(); + queue.push(new changeRichListItemChild()); + queue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <vbox 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=986054" + title="Propagate name change events"> + Mozilla Bug 986054 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <richlistbox> + <richlistitem id="listitem"> + <description id="childcontent" value="This will be changed."/> + </richlistitem> + </richlistbox> + </vbox> +</window> diff --git a/accessible/tests/mochitest/events/test_scroll.xul b/accessible/tests/mochitest/events/test_scroll.xul new file mode 100644 index 000000000..e33161376 --- /dev/null +++ b/accessible/tests/mochitest/events/test_scroll.xul @@ -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"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/chrome-harness.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" + src="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Tests + + function getAnchorJumpInTabDocument(aTabIdx) + { + var tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument(); + return tabDoc.querySelector("a[name='link1']"); + } + + function loadTab(aURL) + { + this.eventSeq = [ + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new asyncInvokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument) + ]; + + this.invoke = function loadTab_invoke() + { + tabBrowser().loadURI(aURL); + } + + this.getID = function loadTab_getID() + { + return "load tab: " + aURL; + } + } + + function loadTabInBackground(aURL) + { + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1) + ]; + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument, 1) + ]; + + this.invoke = function loadTabInBackground_invoke() + { + tabBrowser().loadOneTab(aURL, null, "", null, true); + } + + this.getID = function loadTabInBackground_getID() + { + return "load tab in background: " + aURL; + } + } + + function switchToBackgroundTab() + { + this.eventSeq = [ + new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument) + ]; + + this.invoke = function switchToBackgroundTab_invoke() + { + tabBrowser().selectTabAtIndex(1); + } + + this.getID = function switchToBackgroundTab_getID() + { + return "switch to background tab"; + } + } + + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + + var url = "http://mochi.test:8888/a11y/accessible/tests/mochitest/events/scroll.html#link1"; + gQueue.push(new loadTab(url)); + gQueue.push(new loadTabInBackground(url)); + gQueue.push(new switchToBackgroundTab()); + gQueue.onFinish = function() { closeBrowserWindow(); } + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTest); + ]]> + </script> + + <vbox 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=691734" + title="Make sure scrolling start event is fired when document receive focus"> + Mozilla Bug 691734 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="eventdump"></vbox> + </vbox> +</window> diff --git a/accessible/tests/mochitest/events/test_scroll_caret.xul b/accessible/tests/mochitest/events/test_scroll_caret.xul new file mode 100644 index 000000000..57e27747f --- /dev/null +++ b/accessible/tests/mochitest/events/test_scroll_caret.xul @@ -0,0 +1,91 @@ +<?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"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/chrome-harness.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" + src="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Tests + + function getAnchorJumpInTabDocument(aTabIdx) + { + var tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument(); + return tabDoc.querySelector("h1[id='heading_1']"); + } + + function loadTab(aURL) + { + this.eventSeq = [ + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new asyncCaretMoveChecker(0, getAnchorJumpInTabDocument) + ]; + + this.invoke = function loadTab_invoke() + { + tabBrowser().loadURI(aURL); + } + + this.getID = function loadTab_getID() + { + return "load tab: " + aURL; + } + } + + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + + var url = "http://mochi.test:8888/a11y/accessible/tests/mochitest/events/scroll.html#heading_1"; + gQueue.push(new loadTab(url)); + gQueue.onFinish = function() { closeBrowserWindow(); } + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTest); + ]]> + </script> + + <vbox 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=1056459" + title="Make sure caret move event is fired when document receive focus"> + Mozilla Bug 1056459 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="eventdump"></vbox> + </vbox> +</window> diff --git a/accessible/tests/mochitest/events/test_selection.html b/accessible/tests/mochitest/events/test_selection.html new file mode 100644 index 000000000..de25fedc3 --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection.html @@ -0,0 +1,118 @@ +<html> + +<head> + <title>Accessible selection event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + //gA11yEventDumpToConsole = true; // debuggin + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + // open combobox + gQueue.push(new synthClick("combobox", + new invokerChecker(EVENT_FOCUS, "cb1_item1"))); + gQueue.push(new synthDownKey("cb1_item1", + selChangeSeq("cb1_item1", "cb1_item2"))); + + // closed combobox + gQueue.push(new synthEscapeKey("combobox", + new invokerChecker(EVENT_FOCUS, "combobox"))); + gQueue.push(new synthDownKey("cb1_item2", + selChangeSeq("cb1_item2", "cb1_item3"))); + + // listbox + gQueue.push(new synthClick("lb1_item1", + new invokerChecker(EVENT_SELECTION, "lb1_item1"))); + gQueue.push(new synthDownKey("lb1_item1", + selChangeSeq("lb1_item1", "lb1_item2"))); + + // multiselectable listbox + gQueue.push(new synthClick("lb2_item1", + selChangeSeq(null, "lb2_item1"))); + gQueue.push(new synthDownKey("lb2_item1", + selAddSeq("lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthUpKey("lb2_item2", + selRemoveSeq("lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true }, + selRemoveSeq("lb2_item1"))); + + 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=414302" + title="Incorrect selection events in HTML, XUL and ARIA"> + Bug 414302 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=810268" + title="There's no way to know unselected item when selection in single selection was changed"> + Bug 810268 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="combobox"> + <option id="cb1_item1" value="mushrooms">mushrooms + <option id="cb1_item2" value="greenpeppers">green peppers + <option id="cb1_item3" value="onions" id="onions">onions + <option id="cb1_item4" value="tomatoes">tomatoes + <option id="cb1_item5" value="olives">olives + </select> + + <select id="listbox" size=5> + <option id="lb1_item1" value="mushrooms">mushrooms + <option id="lb1_item2" value="greenpeppers">green peppers + <option id="lb1_item3" value="onions" id="onions">onions + <option id="lb1_item4" value="tomatoes">tomatoes + <option id="lb1_item5" value="olives">olives + </select> + + <p>Pizza</p> + <select id="listbox2" multiple size=5> + <option id="lb2_item1" value="mushrooms">mushrooms + <option id="lb2_item2" value="greenpeppers">green peppers + <option id="lb2_item3" value="onions" id="onions">onions + <option id="lb2_item4" value="tomatoes">tomatoes + <option id="lb2_item5" value="olives">olives + </select> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_selection.xul b/accessible/tests/mochitest/events/test_selection.xul new file mode 100644 index 000000000..2b0c388ff --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection.xul @@ -0,0 +1,255 @@ +<?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="Selection event tests"> + + <script type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + function advanceTab(aTabsID, aDirection, aNextTabID) + { + var eventSeq1 = [ + new invokerChecker(EVENT_SELECTION, aNextTabID) + ] + defineScenario(this, eventSeq1); + + var eventSeq2 = [ + new invokerChecker(EVENT_HIDE, getAccessible(aNextTabID)), + new invokerChecker(EVENT_SHOW, aNextTabID) + ]; + defineScenario(this, eventSeq2); + + this.invoke = function advanceTab_invoke() + { + todo(false, "No accessible recreation should happen, just selection event"); + getNode(aTabsID).advanceSelectedTab(aDirection, true); + } + + this.getID = function synthFocus_getID() + { + return "advanceTab on " + prettyName(aTabsID) + " to " + prettyName(aNextTabID); + } + } + + function select4FirstItems(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(1)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(2)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(3)) + ]; + + this.invoke = function select4FirstItems_invoke() + { + synthesizeKey("VK_DOWN", { shiftKey: true }); // selects two items + synthesizeKey("VK_DOWN", { shiftKey: true }); + synthesizeKey("VK_DOWN", { shiftKey: true }); + } + + this.getID = function select4FirstItems_getID() + { + return "select 4 first items for " + prettyName(aID); + } + } + + function unselect4FirstItems(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(3)), + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(2)), + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(1)), + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0)) + ]; + + this.invoke = function unselect4FirstItems_invoke() + { + synthesizeKey("VK_UP", { shiftKey: true }); + synthesizeKey("VK_UP", { shiftKey: true }); + synthesizeKey("VK_UP", { shiftKey: true }); + synthesizeKey(" ", { ctrlKey: true }); // unselect first item + } + + this.getID = function unselect4FirstItems_getID() + { + return "unselect 4 first items for " + prettyName(aID); + } + } + + function selectAllItems(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode)) + ]; + + this.invoke = function selectAllItems_invoke() + { + synthesizeKey("VK_END", { shiftKey: true }); + } + + this.getID = function selectAllItems_getID() + { + return "select all items for " + prettyName(aID); + } + } + + function unselectAllItemsButFirst(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode)) + ]; + + this.invoke = function unselectAllItemsButFirst_invoke() + { + synthesizeKey("VK_HOME", { shiftKey: true }); + } + + this.getID = function unselectAllItemsButFirst_getID() + { + return "unselect all items for " + prettyName(aID); + } + } + + function unselectSelectItem(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0)) + ]; + + this.invoke = function unselectSelectItem_invoke() + { + synthesizeKey(" ", { ctrlKey: true }); // select item + synthesizeKey(" ", { ctrlKey: true }); // unselect item + } + + this.getID = function unselectSelectItem_getID() + { + return "unselect and then select first item for " + prettyName(aID); + } + } + + /** + * Do tests. + */ + var gQueue = null; + + //enableLogging("events"); + //gA11yEventDumpToConsole = true; // debuggin + + function doTests() + { + gQueue = new eventQueue(); + + ////////////////////////////////////////////////////////////////////////// + // tabbox + gQueue.push(new advanceTab("tabs", 1, "tab3")); + + ////////////////////////////////////////////////////////////////////////// + // listbox + gQueue.push(new synthClick("lb1_item1", + new invokerChecker(EVENT_SELECTION, "lb1_item1"))); + gQueue.push(new synthDownKey("lb1_item1", + new invokerChecker(EVENT_SELECTION, "lb1_item2"))); + + ////////////////////////////////////////////////////////////////////////// + // multiselectable listbox + gQueue.push(new synthClick("lb2_item1", + new invokerChecker(EVENT_SELECTION, "lb2_item1"))); + gQueue.push(new synthDownKey("lb2_item1", + new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthUpKey("lb2_item2", + new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true }, + new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1"))); + + ////////////////////////////////////////////////////////////////////////// + // selection event coalescence + + // fire 4 selection_add events + gQueue.push(new select4FirstItems("listbox2")); + // fire 4 selection_remove events + gQueue.push(new unselect4FirstItems("listbox2")); + // fire selection_within event + gQueue.push(new selectAllItems("listbox2")); + // fire selection_within event + gQueue.push(new unselectAllItemsButFirst("listbox2")); + // fire selection_remove/add events + gQueue.push(new unselectSelectItem("listbox2")); + + 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=414302" + title="Incorrect selection events in HTML, XUL and ARIA"> + Mozilla Bug 414302 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <tabbox id="tabbox" selectedIndex="1"> + <tabs id="tabs"> + <tab id="tab1" label="tab1"/> + <tab id="tab2" label="tab2"/> + <tab id="tab3" label="tab3"/> + <tab id="tab4" label="tab4"/> + </tabs> + <tabpanels> + <tabpanel><!-- tabpanel First elements go here --></tabpanel> + <tabpanel><button id="b1" label="b1"/></tabpanel> + <tabpanel><button id="b2" label="b2"/></tabpanel> + <tabpanel></tabpanel> + </tabpanels> + </tabbox> + + <listbox id="listbox"> + <listitem id="lb1_item1" label="item1"/> + <listitem id="lb1_item2" label="item2"/> + </listbox> + + <listbox id="listbox2" seltype="multiple"> + <listitem id="lb2_item1" label="item1"/> + <listitem id="lb2_item2" label="item2"/> + <listitem id="lb2_item3" label="item3"/> + <listitem id="lb2_item4" label="item4"/> + <listitem id="lb2_item5" label="item5"/> + <listitem id="lb2_item6" label="item6"/> + <listitem id="lb2_item7" label="item7"/> + </listbox> + + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_selection_aria.html b/accessible/tests/mochitest/events/test_selection_aria.html new file mode 100644 index 000000000..aabee46fd --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection_aria.html @@ -0,0 +1,127 @@ +<html> + +<head> + <title>ARIA selection event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function selectItem(aSelectID, aItemID) + { + this.selectNode = getNode(aSelectID); + this.itemNode = getNode(aItemID); + + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION, aItemID) + ]; + + this.invoke = function selectItem_invoke() { + var itemNode = this.selectNode.querySelector("*[aria-selected='true']"); + if (itemNode) + itemNode.removeAttribute("aria-selected"); + + this.itemNode.setAttribute("aria-selected", "true"); + } + + this.getID = function selectItem_getID() + { + return "select item " + prettyName(aItemID); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + + //gA11yEventDumpToConsole = true; // debug stuff + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new selectItem("tablist", "tab1")); + gQueue.push(new selectItem("tablist", "tab2")); + + gQueue.push(new selectItem("tree", "treeitem1")); + gQueue.push(new selectItem("tree", "treeitem1a")); + gQueue.push(new selectItem("tree", "treeitem1a1")); + + gQueue.push(new selectItem("tree2", "tree2item1")); + gQueue.push(new selectItem("tree2", "tree2item1a")); + gQueue.push(new selectItem("tree2", "tree2item1a1")); + + 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=569653" + title="Make selection events async"> + Mozilla Bug 569653 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=804040" + title="Selection event not fired when selection of ARIA tab changes"> + Mozilla Bug 804040 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="tablist" id="tablist"> + <div role="tab" id="tab1">tab1</div> + <div role="tab" id="tab2">tab2</div> + </div> + + <div id="tree" role="tree"> + <div id="treeitem1" role="treeitem">Canada + <div id="treeitem1a" role="treeitem">- Ontario + <div id="treeitem1a1" role="treeitem">-- Toronto</div> + </div> + <div id="treeitem1b" role="treeitem">- Manitoba</div> + </div> + <div id="treeitem2" role="treeitem">Germany</div> + <div id="treeitem3" role="treeitem">Russia</div> + </div> + + <div id="tree2" role="tree" aria-multiselectable="true"> + <div id="tree2item1" role="treeitem">Canada + <div id="tree2item1a" role="treeitem">- Ontario + <div id="tree2item1a1" role="treeitem">-- Toronto</div> + </div> + <div id="tree2item1b" role="treeitem">- Manitoba</div> + </div> + <div id="tree2item2" role="treeitem">Germany</div> + <div id="tree2item3" role="treeitem">Russia</div> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_statechange.html b/accessible/tests/mochitest/events/test_statechange.html new file mode 100644 index 000000000..f4557376b --- /dev/null +++ b/accessible/tests/mochitest/events/test_statechange.html @@ -0,0 +1,287 @@ +<html> + +<head> + <title>Accessible state change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function makeEditableDoc(aDocNode, aIsEnabled) + { + this.DOMNode = aDocNode; + + this.invoke = function editabledoc_invoke() { + // Note: this should fire an EVENT_STATE_CHANGE + this.DOMNode.designMode = 'on'; + }; + + this.check = function editabledoc_check(aEvent) { + + testStates(aDocNode, 0, EXT_STATE_EDITABLE); + + var event = null; + try { + var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent); + } catch (e) { + ok(false, "State change event was expected"); + } + + if (!event) { return; } + + ok(event.isExtraState, "Extra state change was expected"); + is(event.state, EXT_STATE_EDITABLE, "Wrong state of statechange event"); + ok(event.isEnabled, "Expected editable state to be enabled"); + } + + this.getID = function editabledoc_getID() { + return prettyName(aDocNode) + " editable state changed"; + }; + } + + function invalidInput(aNodeOrID) + { + this.DOMNode = getNode(aNodeOrID); + + this.invoke = function invalidInput_invoke() { + // Note: this should fire an EVENT_STATE_CHANGE + this.DOMNode.value = "I am not an email"; + }; + + this.check = function invalidInput_check() { + testStates(aNodeOrID, STATE_INVALID); + }; + + this.getID = function invalidInput_getID() { + return prettyName(aNodeOrID) + " became invalid"; + }; + } + + function changeCheckInput(aID, aIsChecked) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new stateChangeChecker(STATE_CHECKED, false, aIsChecked, this.DOMNode) + ]; + + this.invoke = function changeCheckInput_invoke() + { + this.DOMNode.checked = aIsChecked; + } + + this.getID = function changeCheckInput_getID() + { + return "change checked state to '" + aIsChecked + "' for " + + prettyName(aID); + } + } + + function stateChangeOnFileInput(aID, aAttr, aValue, + aState, aIsExtraState, aIsEnabled) + { + this.fileControlNode = getNode(aID); + this.fileControl = getAccessible(this.fileControlNode); + this.browseButton = this.fileControl.firstChild; + // No state change events on the label. + + this.invoke = function stateChangeOnFileInput_invoke() + { + this.fileControlNode.setAttribute(aAttr, aValue); + } + + this.eventSeq = [ + new stateChangeChecker(aState, aIsExtraState, aIsEnabled, this.fileControl), + new stateChangeChecker(aState, aIsExtraState, aIsEnabled, this.browseButton) + ]; + + this.getID = function stateChangeOnFileInput_getID() + { + return "inherited state change on file input on attribute '" + aAttr + "' change"; + } + } + + function dupeStateChange(aID, aAttr, aValue, + aState, aIsExtraState, aIsEnabled) + { + this.eventSeq = [ + new stateChangeChecker(aState, aIsExtraState, aIsEnabled, getNode(aID)) + ]; + + this.invoke = function dupeStateChange_invoke() + { + getNode(aID).setAttribute(aAttr, aValue); + getNode(aID).setAttribute(aAttr, aValue); + } + + this.getID = function dupeStateChange_getID() + { + return "duped state change events"; + } + } + + function oppositeStateChange(aID, aAttr, aState, aIsExtraState) + { + this.eventSeq = [ ]; + this.unexpectedEventSeq = [ + new stateChangeChecker(aState, aIsExtraState, false, getNode(aID)), + new stateChangeChecker(aState, aIsExtraState, true, getNode(aID)) + ]; + + this.invoke = function oppositeStateChange_invoke() + { + getNode(aID).setAttribute(aAttr, "false"); + getNode(aID).setAttribute(aAttr, "true"); + } + + this.getID = function oppositeStateChange_getID() + { + return "opposite state change events"; + } + } + + /** + * Change concomitant ARIA and native attribute at once. + */ + function echoingStateChange(aID, aARIAAttr, aAttr, aValue, + aState, aIsExtraState, aIsEnabled) + { + this.eventSeq = [ + new stateChangeChecker(aState, aIsExtraState, aIsEnabled, getNode(aID)) + ]; + + this.invoke = function echoingStateChange_invoke() + { + if (aValue == null) { + getNode(aID).removeAttribute(aARIAAttr); + getNode(aID).removeAttribute(aAttr); + + } else { + getNode(aID).setAttribute(aARIAAttr, aValue); + getNode(aID).setAttribute(aAttr, aValue); + } + } + + this.getID = function echoingStateChange_getID() + { + return "enchoing ARIA and native attributes change"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + + // var gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + function doTests() + { + gQueue = new eventQueue(nsIAccessibleEvent.EVENT_STATE_CHANGE); + + // Test delayed editable state change + var doc = document.getElementById("iframe").contentDocument; + gQueue.push(new makeEditableDoc(doc)); + + // invalid state change + gQueue.push(new invalidInput("email")); + + // checked state change + gQueue.push(new changeCheckInput("checkbox", true)); + gQueue.push(new changeCheckInput("checkbox", false)); + gQueue.push(new changeCheckInput("radio", true)); + gQueue.push(new changeCheckInput("radio", false)); + + // file input inherited state changes + gQueue.push(new stateChangeOnFileInput("file", "aria-busy", "true", + STATE_BUSY, false, true)); + gQueue.push(new stateChangeOnFileInput("file", "aria-required", "true", + STATE_REQUIRED, false, true)); + gQueue.push(new stateChangeOnFileInput("file", "aria-invalid", "true", + STATE_INVALID, false, true)); + + gQueue.push(new dupeStateChange("div", "aria-busy", "true", + STATE_BUSY, false, true)); + gQueue.push(new oppositeStateChange("div", "aria-busy", + STATE_BUSY, false)); + + gQueue.push(new echoingStateChange("text1", "aria-disabled", "disabled", "true", + EXT_STATE_ENABLED, true, false)); + gQueue.push(new echoingStateChange("text1", "aria-disabled", "disabled", null, + EXT_STATE_ENABLED, true, true)); + + 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=564471" + title="Make state change events async"> + Bug 564471 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=555728" + title="Fire a11y event based on HTML5 constraint validation"> + Bug 555728 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017" + title="File input control should be propogate states to descendants"> + Bug 699017 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=788389" + title="Fire statechange event whenever checked state is changed not depending on focused state"> + Bug 788389 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=926812" + title="State change event not fired when both disabled and aria-disabled are toggled"> + Bug 926812 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="testContainer"> + <iframe id="iframe"></iframe> + </div> + + <input id="email" type='email'> + + <input id="checkbox" type="checkbox"> + <input id="radio" type="radio"> + + <input id="file" type="file"> + + <div id="div"></div> + + <input id="text1"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_text.html b/accessible/tests/mochitest/events/test_text.html new file mode 100644 index 000000000..d992d6c64 --- /dev/null +++ b/accessible/tests/mochitest/events/test_text.html @@ -0,0 +1,339 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + /** + * Base text remove invoker and checker. + */ + function textRemoveInvoker(aID, aStart, aEnd, aText) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new textChangeChecker(aID, aStart, aEnd, aText, false) + ]; + } + + function textInsertInvoker(aID, aStart, aEnd, aText) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new textChangeChecker(aID, aStart, aEnd, aText, true) + ]; + } + + /** + * Remove inaccessible child node containing accessibles. + */ + function removeChildSpan(aID) + { + this.__proto__ = new textRemoveInvoker(aID, 0, 5, "33322"); + + this.invoke = function removeChildSpan_invoke() + { + // remove HTML span, a first child of the node + this.DOMNode.removeChild(this.DOMNode.firstChild); + } + + this.getID = function removeChildSpan_getID() + { + return "Remove inaccessible span containing accessible nodes" + prettyName(aID); + } + } + + /** + * Insert inaccessible child node containing accessibles. + */ + function insertChildSpan(aID, aInsertAllTogether) + { + this.__proto__ = new textInsertInvoker(aID, 0, 5, "33322"); + + this.invoke = function insertChildSpan_invoke() + { + // <span><span>333</span><span>22</span></span> + if (aInsertAllTogether) { + var topSpan = document.createElement("span"); + var fSpan = document.createElement("span"); + fSpan.textContent = "333"; + topSpan.appendChild(fSpan); + var sSpan = document.createElement("span"); + sSpan.textContent = "22"; + topSpan.appendChild(sSpan); + + this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]); + + } else { + var topSpan = document.createElement("span"); + this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]); + + var fSpan = document.createElement("span"); + fSpan.textContent = "333"; + topSpan.appendChild(fSpan); + + var sSpan = document.createElement("span"); + sSpan.textContent = "22"; + topSpan.appendChild(sSpan); + } + } + + this.getID = function insertChildSpan_getID() + { + return "Insert inaccessible span containing accessibles" + + prettyName(aID); + } + } + + /** + * Remove child embedded accessible. + */ + function removeChildDiv(aID) + { + this.__proto__ = new textRemoveInvoker(aID, 5, 6, kEmbedChar); + + this.invoke = function removeChildDiv_invoke() + { + var childDiv = this.DOMNode.childNodes[1]; + + // Ensure accessible is created to get text remove event when it's + // removed. + getAccessible(childDiv); + + this.DOMNode.removeChild(childDiv); + } + + this.getID = function removeChildDiv_getID() + { + return "Remove accessible div from the middle of text accessible " + + prettyName(aID); + } + } + + /** + * Insert child embedded accessible. + */ + function insertChildDiv(aID) + { + this.__proto__ = new textInsertInvoker(aID, 5, 6, kEmbedChar); + + this.invoke = function insertChildDiv_invoke() + { + var childDiv = document.createElement("div"); + this.DOMNode.insertBefore(childDiv, this.DOMNode.childNodes[1]); + } + + this.getID = function insertChildDiv_getID() + { + return "Insert accessible div into the middle of text accessible " + + prettyName(aID); + } + } + + /** + * Remove children from text container from first to last child or vice + * versa. + */ + function removeChildren(aID, aLastToFirst, aStart, aEnd, aText) + { + this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText); + + this.invoke = function removeChildren_invoke() + { + if (aLastToFirst) { + while (this.DOMNode.firstChild) + this.DOMNode.removeChild(this.DOMNode.lastChild); + } else { + while (this.DOMNode.firstChild) + this.DOMNode.removeChild(this.DOMNode.firstChild); + } + } + + this.getID = function removeChildren_getID() + { + return "remove children of " + prettyName(aID) + + (aLastToFirst ? " from last to first" : " from first to last"); + } + } + + /** + * Remove text from HTML input. + */ + function removeTextFromInput(aID, aStart, aEnd, aText) + { + this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText); + + this.eventSeq.push(new invokerChecker(EVENT_TEXT_VALUE_CHANGE, + this.DOMNode)); + + this.invoke = function removeTextFromInput_invoke() + { + const nsIDOMNSEditableElement = + Components.interfaces.nsIDOMNSEditableElement; + + this.DOMNode.focus(); + this.DOMNode.setSelectionRange(aStart, aEnd); + + synthesizeKey("VK_DELETE", {}); + } + + this.getID = function removeTextFromInput_getID() + { + return "Remove text from " + aStart + " to " + aEnd + " for " + + prettyName(aID); + } + } + + /** + * Add text into HTML input. + */ + function insertTextIntoInput(aID, aStart, aEnd, aText) + { + this.__proto__ = new textInsertInvoker(aID, aStart, aEnd, aText); + + this.eventSeq.push(new invokerChecker(EVENT_TEXT_VALUE_CHANGE, + this.DOMNode)); + + this.invoke = function insertTextIntoInput_invoke() + { + this.DOMNode.focus(); + synthesizeKey("a", {}); + } + + this.getID = function insertTextIntoInput_getID() + { + return "Insert text to " + aStart + " for " + prettyName(aID); + } + } + + /** + * Remove text data from text node of editable area. + */ + function removeTextFromEditable(aID, aStart, aEnd, aText, aTextNode) + { + this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText); + + this.invoke = function removeTextFromEditable_invoke() + { + this.DOMNode.focus(); + + var selection = window.getSelection(); + var range = document.createRange(); + range.setStart(this.textNode, aStart); + range.setEnd(this.textNode, aEnd); + selection.addRange(range); + + synthesizeKey("VK_DELETE", {}); + } + + this.getID = function removeTextFromEditable_getID() + { + return "Remove text from " + aStart + " to " + aEnd + " for " + + prettyName(aID); + } + + this.textNode = getNode(aTextNode); + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + gA11yEventDumpToConsole = true; // debugging + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + // Text remove event on inaccessible child HTML span removal containing + // accessible text nodes. + gQueue.push(new removeChildSpan("p")); + gQueue.push(new insertChildSpan("p"), true); + gQueue.push(new insertChildSpan("p"), false); + + // Remove embedded character. + gQueue.push(new removeChildDiv("div")); + gQueue.push(new insertChildDiv("div")); + + // Remove all children. + var text = kEmbedChar + "txt" + kEmbedChar; + gQueue.push(new removeChildren("div2", true, 0, 5, text)); + gQueue.push(new removeChildren("div3", false, 0, 5, text)); + + // Text remove from text node within hypertext accessible. + gQueue.push(new removeTextFromInput("input", 1, 3, "al")); + gQueue.push(new insertTextIntoInput("input", 1, 2, "a")); + + // bug 570691 + todo(false, "Fix text change events from editable area, see bug 570691"); + //var textNode = getNode("editable").firstChild; + //gQueue.push(new removeTextFromEditable("editable", 1, 3, "al", textNode)); + //textNode = getNode("editable2").firstChild.firstChild; + //gQueue.push(new removeTextFromEditable("editable2", 1, 3, "al", textNode)); + + 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=566293" + title=" wrong length of text remove event when inaccessible node containing accessible nodes is removed"> + Mozilla Bug 566293 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570710" + title="Avoid extra array traversal during text event creation"> + Mozilla Bug 570710 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=574003" + title="Coalesce text events on nodes removal"> + Mozilla Bug 574003 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=575052" + title="Cache text offsets within hypertext accessible"> + Mozilla Bug 575052 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275" + title="Rework accessible tree update code"> + Mozilla Bug 570275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <p id="p"><span><span>333</span><span>22</span></span>1111</p> + <div id="div">hello<div>hello</div>hello</div> + <div id="div2"><div>txt</div>txt<div>txt</div></div> + <div id="div3"><div>txt</div>txt<div>txt</div></div> + <input id="input" value="value"> + <div contentEditable="true" id="editable">value</div> + <div contentEditable="true" id="editable2"><span>value</span></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_text_alg.html b/accessible/tests/mochitest/events/test_text_alg.html new file mode 100644 index 000000000..97428fb3b --- /dev/null +++ b/accessible/tests/mochitest/events/test_text_alg.html @@ -0,0 +1,249 @@ +<html> + +<head> + <title>Accessible text update algorithm testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + const kRemoval = false; + const kInsertion = true; + const kUnexpected = true; + + function changeText(aContainerID, aValue, aEventList) + { + this.containerNode = getNode(aContainerID); + this.textNode = this.containerNode.firstChild; + this.textData = this.textNode.data; + + this.eventSeq = [ ]; + this.unexpectedEventSeq = [ ]; + + for (var i = 0; i < aEventList.length; i++) { + var event = aEventList[i]; + + var isInserted = event[0]; + var str = event[1]; + var offset = event[2]; + var checker = new textChangeChecker(this.containerNode, offset, + offset + str.length, str, + isInserted); + + if (event[3] == kUnexpected) + this.unexpectedEventSeq.push(checker); + else + this.eventSeq.push(checker); + } + + this.invoke = function changeText_invoke() + { + this.textNode.data = aValue; + } + + this.getID = function changeText_getID() + { + return "change text '" + shortenString(this.textData) + "' -> '" + + shortenString(this.textNode.data) + "' for " + + prettyName(this.containerNode); + } + } + + function expStr(x, doublings) + { + for (var i = 0; i < doublings; ++i) + x = x + x; + return x; + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + ////////////////////////////////////////////////////////////////////////// + // wqrema -> tqb: substitution coalesced with removal + + var events = [ + [ kRemoval, "w", 0 ], // wqrema -> qrema + [ kInsertion, "t", 0], // qrema -> tqrema + [ kRemoval, "rema", 2 ], // tqrema -> tq + [ kInsertion, "b", 2] // tq -> tqb + ]; + gQueue.push(new changeText("p1", "tqb", events)); + + ////////////////////////////////////////////////////////////////////////// + // b -> insa: substitution coalesced with insertion (complex substitution) + + events = [ + [ kRemoval, "b", 0 ], // b -> + [ kInsertion, "insa", 0] // -> insa + ]; + gQueue.push(new changeText("p2", "insa", events)); + + ////////////////////////////////////////////////////////////////////////// + // abc -> def: coalesced substitutions + + events = [ + [ kRemoval, "abc", 0 ], // abc -> + [ kInsertion, "def", 0] // -> def + ]; + gQueue.push(new changeText("p3", "def", events)); + + ////////////////////////////////////////////////////////////////////////// + // abcabc -> abcDEFabc: coalesced insertions + + events = [ + [ kInsertion, "DEF", 3] // abcabc -> abcDEFabc + ]; + gQueue.push(new changeText("p4", "abcDEFabc", events)); + + ////////////////////////////////////////////////////////////////////////// + // abc -> defabc: insertion into begin + + events = [ + [ kInsertion, "def", 0] // abc -> defabc + ]; + gQueue.push(new changeText("p5", "defabc", events)); + + ////////////////////////////////////////////////////////////////////////// + // abc -> abcdef: insertion into end + + events = [ + [ kInsertion, "def", 3] // abc -> abcdef + ]; + gQueue.push(new changeText("p6", "abcdef", events)); + + ////////////////////////////////////////////////////////////////////////// + // defabc -> abc: removal from begin + + events = [ + [ kRemoval, "def", 0] // defabc -> abc + ]; + gQueue.push(new changeText("p7", "abc", events)); + + ////////////////////////////////////////////////////////////////////////// + // abcdef -> abc: removal from the end + + events = [ + [ kRemoval, "def", 3] // abcdef -> abc + ]; + gQueue.push(new changeText("p8", "abc", events)); + + ////////////////////////////////////////////////////////////////////////// + // abcDEFabc -> abcabc: coalesced removals + + events = [ + [ kRemoval, "DEF", 3] // abcDEFabc -> abcabc + ]; + gQueue.push(new changeText("p9", "abcabc", events)); + + ////////////////////////////////////////////////////////////////////////// + // !abcdef@ -> @axbcef!: insertion, deletion and substitutions + + events = [ + [ kRemoval, "!", 0 ], // !abcdef@ -> abcdef@ + [ kInsertion, "@", 0], // abcdef@ -> @abcdef@ + [ kInsertion, "x", 2 ], // @abcdef@ -> @axbcdef@ + [ kRemoval, "d", 5], // @axbcdef@ -> @axbcef@ + [ kRemoval, "@", 7 ], // @axbcef@ -> @axbcef + [ kInsertion, "!", 7 ], // @axbcef -> @axbcef! + ]; + gQueue.push(new changeText("p10", "@axbcef!", events)); + + ////////////////////////////////////////////////////////////////////////// + // meilenstein -> levenshtein: insertion, complex and simple substitutions + + events = [ + [ kRemoval, "m", 0 ], // meilenstein -> eilenstein + [ kInsertion, "l", 0], // eilenstein -> leilenstein + [ kRemoval, "il", 2 ], // leilenstein -> leenstein + [ kInsertion, "v", 2], // leenstein -> levenstein + [ kInsertion, "h", 6 ], // levenstein -> levenshtein + ]; + gQueue.push(new changeText("p11", "levenshtein", events)); + + ////////////////////////////////////////////////////////////////////////// + // long strings, remove/insert pair as the old string was replaced on + // new one + + var longStr1 = expStr("x", 16); + var longStr2 = expStr("X", 16); + + var newStr = "a" + longStr1 + "b", insStr = longStr1, rmStr = ""; + events = [ + [ kRemoval, rmStr, 1, kUnexpected ], + [ kInsertion, insStr, 1 ] + ]; + gQueue.push(new changeText("p12", newStr, events)); + + newStr = "a" + longStr2 + "b", insStr = longStr2, rmStr = longStr1; + events = [ + [ kRemoval, rmStr, 1 ], + [ kInsertion, insStr, 1] + ]; + gQueue.push(new changeText("p12", newStr, events)); + + newStr = "ab", insStr = "", rmStr = longStr2; + events = [ + [ kRemoval, rmStr, 1 ], + [ kInsertion, insStr, 1, kUnexpected ] + ]; + gQueue.push(new changeText("p12", newStr, events)); + + 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=626660" + title="Cache rendered text on a11y side"> + Mozilla Bug 626660 + </a> + <br> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <p id="p1">wqrema</p> + <p id="p2">b</p> + <p id="p3">abc</p> + <p id="p4">abcabc</p> + <p id="p5">abc</p> + <p id="p6">abc</p> + <p id="p7">defabc</p> + <p id="p8">abcdef</p> + <p id="p9">abcDEFabc</p> + <p id="p10">!abcdef@</p> + <p id="p11">meilenstein</p> + <p id="p12">ab</p> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_textattrchange.html b/accessible/tests/mochitest/events/test_textattrchange.html new file mode 100644 index 000000000..a69a8df15 --- /dev/null +++ b/accessible/tests/mochitest/events/test_textattrchange.html @@ -0,0 +1,115 @@ +<html> + +<head> + <title>Text attribute changed event for misspelled text</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + 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="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + + const nsIDOMNSEditableElement = + Components.interfaces.nsIDOMNSEditableElement; + + Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm"); + + function spelledTextInvoker(aID) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_TEXT_ATTRIBUTE_CHANGED, this.DOMNode) + ]; + + this.invoke = function spelledTextInvoker_invoke() + { + var editor = this.DOMNode.QueryInterface(nsIDOMNSEditableElement).editor; + var spellChecker = new InlineSpellChecker(editor); + spellChecker.enabled = true; + + //var spellchecker = editor.getInlineSpellChecker(true); + //spellchecker.enableRealTimeSpell = true; + + this.DOMNode.value = "valid text inalid tixt"; + } + + this.finalCheck = function spelledTextInvoker_finalCheck() + { + var defAttrs = buildDefaultTextAttrs(this.DOMNode, kInputFontSize, + kNormalFontWeight, + kInputFontFamily); + testDefaultTextAttrs(aID, defAttrs); + + var attrs = { }; + var misspelledAttrs = { + "invalid": "spelling" + }; + + testTextAttrs(aID, 0, attrs, defAttrs, 0, 11); + testTextAttrs(aID, 11, misspelledAttrs, defAttrs, 11, 17); + testTextAttrs(aID, 17, attrs, defAttrs, 17, 18); + testTextAttrs(aID, 18, misspelledAttrs, defAttrs, 18, 22); + } + + this.getID = function spelledTextInvoker_getID() + { + return "text attribute change for misspelled text"; + } + } + + /** + * Do tests. + */ + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTests() + { + // Synth focus before spellchecking turning on to make sure editor + // gets a time for initialization. + + gQueue = new eventQueue(); + gQueue.push(new synthFocus("input")); + gQueue.push(new spelledTextInvoker("input")); + 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=345759" + title="Implement text attributes"> + Mozilla Bug 345759 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input"/> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_textselchange.html b/accessible/tests/mochitest/events/test_textselchange.html new file mode 100644 index 000000000..92ac30423 --- /dev/null +++ b/accessible/tests/mochitest/events/test_textselchange.html @@ -0,0 +1,86 @@ +<html> + +<head> + <title>Accessible text selection change events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + var gQueue = null; + + // gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + function getOnclickSeq(aID) + { + return [ + new caretMoveChecker(0, aID), + new unexpectedInvokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID) + ]; + } + + function doTests() + { + // test caret move events and caret offsets + gQueue = new eventQueue(); + + gQueue.push(new synthClick("c1_p1", getOnclickSeq("c1_p1"))); + gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 1), { shiftKey: true })); + gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 2), { shiftKey: true })); + + gQueue.push(new synthClick("ta1", getOnclickSeq("ta1"))); + gQueue.push(new synthRightKey("ta1", + new textSelectionChecker("ta1", 0, 1), + { shiftKey: true })); + gQueue.push(new synthLeftKey("ta1", + [new textSelectionChecker("ta1", 0, 0), + new caretMoveChecker(0, "ta1")])); + + 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=762934" + title="Text selection change event has a wrong target when selection is spanned through several objects"> + Bug 762934 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=956032" + title="Text selection change event missed when selected text becomes unselected"> + Bug 956032 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="c1" contentEditable="true"> + <p id="c1_p1">paragraph</p> + <p id="c1_p2">paragraph</p> + </div> + + <textarea id="ta1">Hello world</textarea> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_tree.xul b/accessible/tests/mochitest/events/test_tree.xul new file mode 100644 index 000000000..797e7bf97 --- /dev/null +++ b/accessible/tests/mochitest/events/test_tree.xul @@ -0,0 +1,348 @@ +<?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="DOM TreeRowCountChanged and a11y name change events."> + + <script type="application/javascript" + 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="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Invoker's checkers + + /** + * Check TreeRowCountChanged event. + */ + function rowCountChangedChecker(aMsg, aIdx, aCount) + { + this.type = "TreeRowCountChanged"; + this.target = gTree; + this.check = function check(aEvent) + { + var propBag = aEvent.detail.QueryInterface(Components.interfaces.nsIPropertyBag2); + var index = propBag.getPropertyAsInt32("index"); + is(index, aIdx, "Wrong 'index' data of 'treeRowCountChanged' event."); + + var count = propBag.getPropertyAsInt32("count"); + is(count, aCount, "Wrong 'count' data of 'treeRowCountChanged' event."); + } + this.getID = function getID() + { + return aMsg + "TreeRowCountChanged"; + } + } + + /** + * Check TreeInvalidated event. + */ + function treeInvalidatedChecker(aMsg, aStartRow, aEndRow, aStartCol, aEndCol) + { + this.type = "TreeInvalidated"; + this.target = gTree; + this.check = function check(aEvent) + { + var propBag = aEvent.detail.QueryInterface(Components.interfaces.nsIPropertyBag2); + try { + var startRow = propBag.getPropertyAsInt32("startrow"); + } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') { + startRow = null; + } + is(startRow, aStartRow, + "Wrong 'startrow' of 'treeInvalidated' event on " + aMsg); + + try { + var endRow = propBag.getPropertyAsInt32("endrow"); + } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') { + endRow = null; + } + is(endRow, aEndRow, + "Wrong 'endrow' of 'treeInvalidated' event on " + aMsg); + + try { + var startCol = propBag.getPropertyAsInt32("startcolumn"); + } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') { + startCol = null; + } + is(startCol, aStartCol, + "Wrong 'startcolumn' of 'treeInvalidated' event on " + aMsg); + + try { + var endCol = propBag.getPropertyAsInt32("endcolumn"); + } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') { + endCol = null; + } + is(endCol, aEndCol, + "Wrong 'endcolumn' of 'treeInvalidated' event on " + aMsg); + } + this.getID = function getID() + { + return "TreeInvalidated on " + aMsg; + } + } + + /** + * Check name changed a11y event. + */ + function nameChangeChecker(aMsg, aRow, aCol) + { + this.type = EVENT_NAME_CHANGE; + + function targetGetter() + { + var acc = getAccessible(gTree); + + var tableAcc = getAccessible(acc, [nsIAccessibleTable]); + return tableAcc.getCellAt(aRow, aCol); + } + Object.defineProperty(this, "target", { get: targetGetter }); + + this.getID = function getID() + { + return aMsg + "name changed"; + } + } + + /** + * Check name changed a11y event for a row. + */ + function rowNameChangeChecker(aMsg, aRow) + { + this.type = EVENT_NAME_CHANGE; + + function targetGetter() + { + var acc = getAccessible(gTree); + return acc.getChildAt(aRow + 1); + } + Object.defineProperty(this, "target", { get: targetGetter }); + + this.getID = function getID() + { + return aMsg + "name changed"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + /** + * Set tree view. + */ + function setTreeView() + { + this.invoke = function setTreeView_invoke() + { + gTreeBox.view = gView; + } + + this.getID = function setTreeView_getID() { return "set tree view"; } + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, gTree) + ]; + }; + + /** + * Insert row at 0 index and checks TreeRowCountChanged and TreeInvalidated + * event. + */ + function insertRow() + { + this.invoke = function insertRow_invoke() + { + gView.appendItem("last"); + gTreeBox.rowCountChanged(0, 1); + } + + this.eventSeq = + [ + new rowCountChangedChecker("insertRow: ", 0, 1), + new treeInvalidatedChecker("insertRow", 0, 5, null, null) + ]; + + this.getID = function insertRow_getID() + { + return "insert row"; + } + } + + /** + * Invalidates first column and checks six name changed events for each + * treeitem plus TreeInvalidated event. + */ + function invalidateColumn() + { + this.invoke = function invalidateColumn_invoke() + { + // Make sure accessible subtree of XUL tree is created otherwise no + // name change events for cell accessibles are emitted. + var tree = getAccessible(gTree); + var child = tree.firstChild; + var walkDown = true; + while (child != tree) { + if (walkDown) { + var grandChild = child.firstChild; + if (grandChild) { + child = grandChild; + continue; + } + } + + var sibling = child.nextSibling; + if (sibling) { + child = sibling; + walkDown = true; + continue; + } + + child = child.parent; + walkDown = false; + } + + // Fire 'TreeInvalidated' event by InvalidateColumn() + var firstCol = gTree.columns.getFirstColumn(); + for (var i = 0; i < gView.rowCount; i++) + gView.setCellText(i, firstCol, "hey " + String(i) + "x0"); + + gTreeBox.invalidateColumn(firstCol); + } + + this.eventSeq = + [ + new nameChangeChecker("invalidateColumn: ", 0, 0), + new nameChangeChecker("invalidateColumn: ", 1, 0), + new nameChangeChecker("invalidateColumn: ", 2, 0), + new nameChangeChecker("invalidateColumn: ", 3, 0), + new nameChangeChecker("invalidateColumn: ", 4, 0), + new nameChangeChecker("invalidateColumn: ", 5, 0), + new treeInvalidatedChecker("invalidateColumn", null, null, 0, 0) + ]; + + this.getID = function invalidateColumn_getID() + { + return "invalidate column"; + } + } + + /** + * Invalidates second row and checks name changed event for first treeitem + * (note, there are two name changed events on linux due to different + * accessible tree for xul:tree element) plus TreeInvalidated event. + */ + function invalidateRow() + { + this.invoke = function invalidateRow_invoke() + { + // Fire 'TreeInvalidated' event by InvalidateRow() + var colCount = gTree.columns.count; + var column = gTree.columns.getFirstColumn(); + while (column) { + gView.setCellText(1, column, "aloha 1x" + String(column.index)); + column = column.getNext(); + } + + gTreeBox.invalidateRow(1); + } + + this.eventSeq = + [ + new nameChangeChecker("invalidateRow: ", 1, 0), + new nameChangeChecker("invalidateRow: ", 1, 1), + new rowNameChangeChecker("invalidateRow: ", 1), + new treeInvalidatedChecker("invalidateRow", 1, 1, null, null) + ]; + + this.getID = function invalidateRow_getID() + { + return "invalidate row"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + var gTree = null; + var gTreeBox = null; + var gTreeView = null; + var gQueue = null; + + // gA11yEventDumpID = "debug"; + gA11yEventDumpToConsole = true; // debuggin + + function doTest() + { + // Initialize the tree + gTree = document.getElementById("tree"); + gTreeBox = gTree.treeBoxObject; + gView = new nsTableTreeView(5); + + // Perform actions + gQueue = new eventQueue(); + + gQueue.push(new setTreeView()); + gQueue.push(new insertRow()); + gQueue.push(new invalidateColumn()); + gQueue.push(new invalidateRow()); + + gQueue.invoke(); + } + + 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=368835" + title="Fire TreeViewChanged/TreeRowCountChanged events."> + Mozilla Bug 368835 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=308564" + title="No accessibility events when data in a tree row changes."> + Mozilla Bug 308564 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=739524" + title="replace TreeViewChanged DOM event on direct call from XUL tree."> + Mozilla Bug 739524 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=743568" + title="Thunderbird message list tree emitting incorrect focus signals after message deleted."> + Mozilla Bug 743568 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="debug"/> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col1" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="column 2"/> + </treecols> + <treechildren id="treechildren"/> + </tree> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/events/test_valuechange.html b/accessible/tests/mochitest/events/test_valuechange.html new file mode 100644 index 000000000..3464ffdeb --- /dev/null +++ b/accessible/tests/mochitest/events/test_valuechange.html @@ -0,0 +1,255 @@ +<html> + +<head> + <title>Accessible value change events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript" + src="../value.js"></script> + + <script type="application/javascript"> + /** + * Do tests. + */ + var gQueue = null; + + // Value change invoker + function changeARIAValue(aNodeOrID, aValuenow, aValuetext) + { + this.DOMNode = getNode(aNodeOrID); + this.eventSeq = [ new invokerChecker(aValuetext ? + EVENT_TEXT_VALUE_CHANGE : + EVENT_VALUE_CHANGE, this.DOMNode) + ]; + + this.invoke = function changeARIAValue_invoke() { + + // Note: this should not fire an EVENT_VALUE_CHANGE when aria-valuetext + // is not empty + if (aValuenow != undefined) + this.DOMNode.setAttribute("aria-valuenow", aValuenow); + + // Note: this should always fire an EVENT_VALUE_CHANGE + if (aValuetext != undefined) + this.DOMNode.setAttribute("aria-valuetext", aValuetext); + } + + this.check = function changeARIAValue_check() { + var acc = getAccessible(aNodeOrID, [nsIAccessibleValue]); + if (!acc) + return; + + // Note: always test against valuetext first because the existence of + // aria-valuetext takes precedence over aria-valuenow in gecko. + is(acc.value, (aValuetext != undefined)? aValuetext : aValuenow, + "Wrong value of " + prettyName(aNodeOrID)); + } + + this.getID = function changeARIAValue_getID() { + return prettyName(aNodeOrID) + " value changed"; + } + } + + function changeValue(aID, aValue) + { + this.DOMNode = getNode(aID); + this.eventSeq = [new invokerChecker(EVENT_TEXT_VALUE_CHANGE, + this.DOMNode) + ]; + + this.invoke = function changeValue_invoke() + { + this.DOMNode.value = aValue; + } + + this.check = function changeValue_check() + { + var acc = getAccessible(this.DOMNode); + is(acc.value, aValue, "Wrong value for " + prettyName(aID)); + } + + this.getID = function changeValue_getID() + { + return prettyName(aID) + " value changed"; + } + } + + function changeProgressValue(aID, aValue) + { + this.DOMNode = getNode(aID); + this.eventSeq = [new invokerChecker(EVENT_VALUE_CHANGE, this.DOMNode)]; + + this.invoke = function changeProgressValue_invoke() + { + this.DOMNode.value = aValue; + } + + this.check = function changeProgressValue_check() + { + var acc = getAccessible(this.DOMNode); + is(acc.value, aValue+"%", "Wrong value for " + prettyName(aID)); + } + + this.getID = function changeProgressValue_getID() + { + return prettyName(aID) + " value changed"; + } + } + + function changeRangeValue(aID) + { + this.DOMNode = getNode(aID); + this.eventSeq = [new invokerChecker(EVENT_VALUE_CHANGE, this.DOMNode)]; + + this.invoke = function changeRangeValue_invoke() + { + synthesizeMouse(getNode(aID), 5, 5, { }); + } + + this.finalCheck = function changeRangeValue_finalCheck() + { + var acc = getAccessible(this.DOMNode); + is(acc.value, "0", "Wrong value for " + prettyName(aID)); + } + + this.getID = function changeRangeValue_getID() + { + return prettyName(aID) + " range value changed"; + } + } + + function changeSelectValue(aID, aKey, aValue) + { + this.eventSeq = + [ new invokerChecker(EVENT_TEXT_VALUE_CHANGE, getAccessible(aID)) ]; + + this.invoke = function changeSelectValue_invoke() + { + getNode(aID).focus(); + synthesizeKey(aKey, {}, window); + } + + this.finalCheck = function changeSelectValue_finalCheck() + { + is(getAccessible(aID).value, aValue, "Wrong value for " + prettyName(aID)); + } + + this.getID = function changeSelectValue_getID() + { + return `${prettyName(aID)} closed select value change on '${aKey}'' key press`; + } + } + + //enableLogging("DOMEvents"); + //gA11yEventDumpToConsole = true; + function doTests() + { + // Test initial values + testValue("slider_vn", "5", 5, 0, 1000, 0); + testValue("slider_vnvt", "plain", 0, 0, 5, 0); + testValue("slider_vt", "hi", 0, 0, 3, 0); + testValue("scrollbar", "5", 5, 0, 1000, 0); + testValue("progress", "22%", 22, 0, 100, 0); + testValue("range", "6", 6, 0, 10, 1); + + // Test value change events + gQueue = new eventQueue(); + + gQueue.push(new changeARIAValue("slider_vn", "6", undefined)); + gQueue.push(new changeARIAValue("slider_vt", undefined, "hey!")); + gQueue.push(new changeARIAValue("slider_vnvt", "3", "sweet")); + gQueue.push(new changeARIAValue("scrollbar", "6", undefined)); + + gQueue.push(new changeValue("combobox", "hello")); + + gQueue.push(new changeProgressValue("progress", "50")); + gQueue.push(new changeRangeValue("range")); + + gQueue.push(new changeSelectValue("select", "VK_DOWN", "2nd")); + gQueue.push(new changeSelectValue("select", "3", "3rd")); + + 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=478032" + title=" Fire delayed value changed event for aria-valuetext changes"> + Mozilla Bug 478032 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289" + title="We dont expose new aria role 'scrollbar' and property aria-orientation"> + Mozilla Bug 529289 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764" + title="Make HTML5 input@type=range element accessible"> + Mozilla Bug 559764 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=703202" + title="ARIA comboboxes don't fire value change events"> + Mozilla Bug 703202 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=761901" + title=" HTML5 progress accessible should fire value change event"> + Mozilla Bug 761901 + </a> + + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <!-- ARIA sliders --> + <div id="slider_vn" role="slider" aria-valuenow="5" + aria-valuemin="0" aria-valuemax="1000">slider</div> + + <div id="slider_vt" role="slider" aria-valuetext="hi" + aria-valuemin="0" aria-valuemax="3">greeting slider</div> + + <div id="slider_vnvt" role="slider" aria-valuenow="0" aria-valuetext="plain" + aria-valuemin="0" aria-valuemax="5">sweetness slider</div> + + <!-- ARIA scrollbar --> + <div id="scrollbar" role="scrollbar" aria-valuenow="5" + aria-valuemin="0" aria-valuemax="1000">slider</div> + + <!-- ARIA combobox --> + <input id="combobox" role="combobox" aria-autocomplete="inline"> + + <!-- progress bar --> + <progress id="progress" value="22" max="100"></progress> + + <!-- input@type="range" --> + <input type="range" id="range" min="0" max="10" value="6"> + + <select id="select"> + <option>1st</option> + <option>2nd</option> + <option>3rd</option> + </select> +</body> +</html> |