diff options
Diffstat (limited to 'accessible/tests/mochitest/events/test_focus_autocomplete.xul')
-rw-r--r-- | accessible/tests/mochitest/events/test_focus_autocomplete.xul | 518 |
1 files changed, 518 insertions, 0 deletions
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> |