<?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>