1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xul Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,514 @@ 1.4 +<?xml version="1.0"?> 1.5 +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> 1.6 +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" 1.7 + type="text/css"?> 1.8 + 1.9 +<!-- Firefox searchbar --> 1.10 +<?xml-stylesheet href="chrome://browser/content/browser.css" 1.11 + type="text/css"?> 1.12 +<!-- SeaMonkey searchbar --> 1.13 +<?xml-stylesheet href="chrome://navigator/content/navigator.css" 1.14 + type="text/css"?> 1.15 + 1.16 +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 1.17 + title="Accessible focus event testing"> 1.18 + 1.19 + <script type="application/javascript" 1.20 + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> 1.21 + <script type="application/javascript" 1.22 + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> 1.23 + 1.24 + <script type="application/javascript" 1.25 + src="../common.js" /> 1.26 + <script type="application/javascript" 1.27 + src="../role.js" /> 1.28 + <script type="application/javascript" 1.29 + src="../states.js" /> 1.30 + <script type="application/javascript" 1.31 + src="../events.js" /> 1.32 + 1.33 + <script type="application/javascript" 1.34 + src="../autocomplete.js" /> 1.35 + 1.36 + <script type="application/javascript"> 1.37 + <![CDATA[ 1.38 + //////////////////////////////////////////////////////////////////////////// 1.39 + // Hacky stuffs 1.40 + 1.41 + // This is the hack needed for searchbar work outside of browser. 1.42 + function getBrowser() 1.43 + { 1.44 + return { 1.45 + mCurrentBrowser: { engines: new Array() } 1.46 + }; 1.47 + } 1.48 + 1.49 + //////////////////////////////////////////////////////////////////////////// 1.50 + // Invokers 1.51 + 1.52 + function loadFormAutoComplete(aIFrameID) 1.53 + { 1.54 + this.iframeNode = getNode(aIFrameID); 1.55 + this.iframe = getAccessible(aIFrameID); 1.56 + 1.57 + this.eventSeq = [ 1.58 + new invokerChecker(EVENT_REORDER, this.iframe) 1.59 + ]; 1.60 + 1.61 + this.invoke = function loadFormAutoComplete_invoke() 1.62 + { 1.63 + var url = "data:text/html,<html><body><form id='form'>" + 1.64 + "<input id='input' name='a11ytest-formautocomplete'>" + 1.65 + "</form></body></html>"; 1.66 + this.iframeNode.setAttribute("src", url); 1.67 + } 1.68 + 1.69 + this.getID = function loadFormAutoComplete_getID() 1.70 + { 1.71 + return "load form autocomplete page"; 1.72 + } 1.73 + } 1.74 + 1.75 + function initFormAutoCompleteBy(aIFrameID, aAutoCompleteValue) 1.76 + { 1.77 + this.iframe = getAccessible(aIFrameID); 1.78 + 1.79 + this.eventSeq = [ 1.80 + new invokerChecker(EVENT_REORDER, this.iframe) 1.81 + ]; 1.82 + 1.83 + this.invoke = function initFormAutoCompleteBy_invoke() 1.84 + { 1.85 + var iframeDOMDoc = getIFrameDOMDoc(aIFrameID); 1.86 + 1.87 + var inputNode = iframeDOMDoc.getElementById("input"); 1.88 + inputNode.value = aAutoCompleteValue; 1.89 + var formNode = iframeDOMDoc.getElementById("form"); 1.90 + formNode.submit(); 1.91 + } 1.92 + 1.93 + this.getID = function initFormAutoCompleteBy_getID() 1.94 + { 1.95 + return "init form autocomplete by '" + aAutoCompleteValue + "'"; 1.96 + } 1.97 + } 1.98 + 1.99 + function loadHTML5ListAutoComplete(aIFrameID) 1.100 + { 1.101 + this.iframeNode = getNode(aIFrameID); 1.102 + this.iframe = getAccessible(aIFrameID); 1.103 + 1.104 + this.eventSeq = [ 1.105 + new invokerChecker(EVENT_REORDER, this.iframe) 1.106 + ]; 1.107 + 1.108 + this.invoke = function loadHTML5ListAutoComplete_invoke() 1.109 + { 1.110 + var url = "data:text/html,<html><body>" + 1.111 + "<datalist id='cities'><option>hello</option><option>hi</option></datalist>" + 1.112 + "<input id='input' list='cities'>" + 1.113 + "</body></html>"; 1.114 + this.iframeNode.setAttribute("src", url); 1.115 + } 1.116 + 1.117 + this.getID = function loadHTML5ListAutoComplete_getID() 1.118 + { 1.119 + return "load HTML5 list autocomplete page"; 1.120 + } 1.121 + } 1.122 + 1.123 + function removeChar(aID, aCheckerOrEventSeq) 1.124 + { 1.125 + this.__proto__ = new synthAction(aID, aCheckerOrEventSeq); 1.126 + 1.127 + this.invoke = function removeChar_invoke() 1.128 + { 1.129 + synthesizeKey("VK_LEFT", { shiftKey: true }); 1.130 + synthesizeKey("VK_DELETE", {}); 1.131 + } 1.132 + 1.133 + this.getID = function removeChar_getID() 1.134 + { 1.135 + return "remove char on " + prettyName(aID); 1.136 + } 1.137 + } 1.138 + 1.139 + function replaceOnChar(aID, aChar, aCheckerOrEventSeq) 1.140 + { 1.141 + this.__proto__ = new synthAction(aID, aCheckerOrEventSeq); 1.142 + 1.143 + this.invoke = function replaceOnChar_invoke() 1.144 + { 1.145 + this.DOMNode.select(); 1.146 + synthesizeKey(aChar, {}); 1.147 + } 1.148 + 1.149 + this.getID = function replaceOnChar_getID() 1.150 + { 1.151 + return "replace on char '" + aChar + "' for" + prettyName(aID); 1.152 + } 1.153 + } 1.154 + 1.155 + function focusOnMouseOver(aIDFunc, aIDFuncArg) 1.156 + { 1.157 + this.eventSeq = [ new focusChecker(aIDFunc, aIDFuncArg) ]; 1.158 + 1.159 + this.invoke = function focusOnMouseOver_invoke() 1.160 + { 1.161 + this.id = aIDFunc.call(null, aIDFuncArg); 1.162 + this.node = getNode(this.id); 1.163 + this.window = this.node.ownerDocument.defaultView; 1.164 + 1.165 + if (this.node.localName == "tree") { 1.166 + var tree = getAccessible(this.node); 1.167 + var accessible = getAccessible(this.id); 1.168 + if (tree != accessible) { 1.169 + var itemX = {}, itemY = {}, treeX = {}, treeY = {}; 1.170 + accessible.getBounds(itemX, itemY, {}, {}); 1.171 + tree.getBounds(treeX, treeY, {}, {}); 1.172 + this.x = itemX.value - treeX.value; 1.173 + this.y = itemY.value - treeY.value; 1.174 + } 1.175 + } 1.176 + 1.177 + // Generate mouse move events in timeouts until autocomplete popup list 1.178 + // doesn't have it, the reason is do that because autocomplete popup 1.179 + // ignores mousemove events firing in too short range. 1.180 + synthesizeMouse(this.node, this.x, this.y, { type: "mousemove" }); 1.181 + this.doMouseMoveFlood(this); 1.182 + } 1.183 + 1.184 + this.finalCheck = function focusOnMouseOver_getID() 1.185 + { 1.186 + this.isFlooding = false; 1.187 + } 1.188 + 1.189 + this.getID = function focusOnMouseOver_getID() 1.190 + { 1.191 + return "mouse over on " + prettyName(aIDFunc.call(null, aIDFuncArg)); 1.192 + } 1.193 + 1.194 + this.doMouseMoveFlood = function focusOnMouseOver_doMouseMoveFlood(aThis) 1.195 + { 1.196 + synthesizeMouse(aThis.node, aThis.x + 1, aThis.y + 1, 1.197 + { type: "mousemove" }, aThis.window); 1.198 + 1.199 + if (aThis.isFlooding) 1.200 + aThis.window.setTimeout(aThis.doMouseMoveFlood, 0, aThis); 1.201 + } 1.202 + 1.203 + this.id = null; 1.204 + this.node = null; 1.205 + this.window = null; 1.206 + 1.207 + this.isFlooding = true; 1.208 + this.x = 1; 1.209 + this.y = 1; 1.210 + } 1.211 + 1.212 + function selectByClick(aIDFunc, aIDFuncArg, 1.213 + aFocusTargetFunc, aFocusTargetFuncArg) 1.214 + { 1.215 + this.eventSeq = [ new focusChecker(aFocusTargetFunc, aFocusTargetFuncArg) ]; 1.216 + 1.217 + this.invoke = function selectByClick_invoke() 1.218 + { 1.219 + var id = aIDFunc.call(null, aIDFuncArg); 1.220 + var node = getNode(id); 1.221 + var targetWindow = node.ownerDocument.defaultView; 1.222 + 1.223 + var x = 0, y = 0; 1.224 + if (node.localName == "tree") { 1.225 + var tree = getAccessible(node); 1.226 + var accessible = getAccessible(id); 1.227 + if (tree != accessible) { 1.228 + var itemX = {}, itemY = {}, treeX = {}, treeY = {}; 1.229 + accessible.getBounds(itemX, itemY, {}, {}); 1.230 + tree.getBounds(treeX, treeY, {}, {}); 1.231 + x = itemX.value - treeX.value; 1.232 + y = itemY.value - treeY.value; 1.233 + } 1.234 + } 1.235 + 1.236 + synthesizeMouseAtCenter(node, {}, targetWindow); 1.237 + } 1.238 + 1.239 + this.getID = function selectByClick_getID() 1.240 + { 1.241 + return "select by click " + prettyName(aIDFunc.call(null, aIDFuncArg)); 1.242 + } 1.243 + } 1.244 + 1.245 + //////////////////////////////////////////////////////////////////////////// 1.246 + // Target getters 1.247 + 1.248 + function getItem(aItemObj) 1.249 + { 1.250 + var autocomplete = aItemObj.autocomplete; 1.251 + var autocompleteNode = aItemObj.autocompleteNode; 1.252 + 1.253 + // XUL searchbar 1.254 + if (autocompleteNode.localName == "searchbar") { 1.255 + var popupNode = autocompleteNode._popup; 1.256 + if (popupNode) { 1.257 + var list = getAccessible(popupNode); 1.258 + return list.getChildAt(aItemObj.index); 1.259 + } 1.260 + } 1.261 + 1.262 + // XUL autocomplete 1.263 + var popupNode = autocompleteNode.popup; 1.264 + if (!popupNode) { 1.265 + // HTML form autocomplete 1.266 + var controller = Components.classes["@mozilla.org/autocomplete/controller;1"]. 1.267 + getService(Components.interfaces.nsIAutoCompleteController); 1.268 + popupNode = controller.input.popup.QueryInterface(nsIDOMNode); 1.269 + } 1.270 + 1.271 + if (popupNode) { 1.272 + if ("richlistbox" in popupNode) { 1.273 + var list = getAccessible(popupNode.richlistbox); 1.274 + return list.getChildAt(aItemObj.index); 1.275 + } 1.276 + 1.277 + var list = getAccessible(popupNode.tree); 1.278 + return list.getChildAt(aItemObj.index + 1); 1.279 + } 1.280 + } 1.281 + 1.282 + function getTextEntry(aID) 1.283 + { 1.284 + // For form autocompletes the autocomplete widget and text entry widget 1.285 + // is the same widget, for XUL autocompletes the text entry is a first 1.286 + // child. 1.287 + var localName = getNode(aID).localName; 1.288 + 1.289 + // XUL autocomplete 1.290 + if (localName == "textbox") 1.291 + return getAccessible(aID).firstChild; 1.292 + 1.293 + // HTML form autocomplete 1.294 + if (localName == "input") 1.295 + return getAccessible(aID); 1.296 + 1.297 + // XUL searchbar 1.298 + if (localName == "searchbar") 1.299 + return getAccessible(getNode(aID).textbox.inputField); 1.300 + 1.301 + return null; 1.302 + } 1.303 + 1.304 + function itemObj(aID, aIdx) 1.305 + { 1.306 + this.autocompleteNode = getNode(aID); 1.307 + 1.308 + this.autocomplete = this.autocompleteNode.localName == "searchbar" ? 1.309 + getAccessible(this.autocompleteNode.textbox) : 1.310 + getAccessible(this.autocompleteNode); 1.311 + 1.312 + this.index = aIdx; 1.313 + } 1.314 + 1.315 + function getIFrameDOMDoc(aIFrameID) 1.316 + { 1.317 + return getNode(aIFrameID).contentDocument; 1.318 + } 1.319 + 1.320 + //////////////////////////////////////////////////////////////////////////// 1.321 + // Test helpers 1.322 + 1.323 + function queueAutoCompleteTests(aID) 1.324 + { 1.325 + // focus autocomplete text entry 1.326 + gQueue.push(new synthFocus(aID, new focusChecker(getTextEntry, aID))); 1.327 + 1.328 + // open autocomplete popup 1.329 + gQueue.push(new synthDownKey(aID, new nofocusChecker())); 1.330 + 1.331 + // select second option ('hi' option), focus on it 1.332 + gQueue.push(new synthUpKey(aID, 1.333 + new focusChecker(getItem, new itemObj(aID, 1)))); 1.334 + 1.335 + // choose selected option, focus on text entry 1.336 + gQueue.push(new synthEnterKey(aID, new focusChecker(getTextEntry, aID))); 1.337 + 1.338 + // remove char, autocomplete popup appears 1.339 + gQueue.push(new removeChar(aID, new nofocusChecker())); 1.340 + 1.341 + // select first option ('hello' option), focus on it 1.342 + gQueue.push(new synthDownKey(aID, 1.343 + new focusChecker(getItem, new itemObj(aID, 0)))); 1.344 + 1.345 + // mouse move on second option ('hi' option), focus on it 1.346 + gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 1))); 1.347 + 1.348 + // autocomplete popup updated (no selected item), focus on textentry 1.349 + gQueue.push(new synthKey(aID, "e", null, new focusChecker(getTextEntry, aID))); 1.350 + 1.351 + // select first option ('hello' option), focus on it 1.352 + gQueue.push(new synthDownKey(aID, 1.353 + new focusChecker(getItem, new itemObj(aID, 0)))); 1.354 + 1.355 + // popup gets hidden, focus on textentry 1.356 + gQueue.push(new synthRightKey(aID, new focusChecker(getTextEntry, aID))); 1.357 + 1.358 + // popup gets open, no focus 1.359 + gQueue.push(new synthOpenComboboxKey(aID, new nofocusChecker())); 1.360 + 1.361 + // select first option again ('hello' option), focus on it 1.362 + gQueue.push(new synthDownKey(aID, 1.363 + new focusChecker(getItem, new itemObj(aID, 0)))); 1.364 + 1.365 + // no option is selected, focus on text entry 1.366 + gQueue.push(new synthUpKey(aID, new focusChecker(getTextEntry, aID))); 1.367 + 1.368 + // close popup, no focus 1.369 + gQueue.push(new synthEscapeKey(aID, new nofocusChecker())); 1.370 + 1.371 + // autocomplete popup appears (no selected item), focus stays on textentry 1.372 + gQueue.push(new replaceOnChar(aID, "h", new nofocusChecker())); 1.373 + 1.374 + // mouse move on first option ('hello' option), focus on it 1.375 + gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 0))); 1.376 + 1.377 + // click first option ('hello' option), popup closes, focus on text entry 1.378 + gQueue.push(new selectByClick(getItem, new itemObj(aID, 0), getTextEntry, aID)); 1.379 + } 1.380 + 1.381 + //////////////////////////////////////////////////////////////////////////// 1.382 + // Tests 1.383 + 1.384 + //gA11yEventDumpID = "eventdump"; // debug stuff 1.385 + //gA11yEventDumpToConsole = true; // debug stuff 1.386 + 1.387 + var gInitQueue = null; 1.388 + function initTests() 1.389 + { 1.390 + if (SEAMONKEY || MAC) { 1.391 + todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237), and on Mac (bug 746177)"); 1.392 + shutdownAutoComplete(); 1.393 + SimpleTest.finish(); 1.394 + return; 1.395 + } 1.396 + 1.397 + gInitQueue = new eventQueue(); 1.398 + gInitQueue.push(new loadFormAutoComplete("iframe")); 1.399 + gInitQueue.push(new initFormAutoCompleteBy("iframe", "hello")); 1.400 + gInitQueue.push(new initFormAutoCompleteBy("iframe", "hi")); 1.401 + gInitQueue.push(new loadHTML5ListAutoComplete("iframe2")); 1.402 + gInitQueue.onFinish = function initQueue_onFinish() 1.403 + { 1.404 + SimpleTest.executeSoon(doTests); 1.405 + return DO_NOT_FINISH_TEST; 1.406 + } 1.407 + gInitQueue.invoke(); 1.408 + } 1.409 + 1.410 + var gQueue = null; 1.411 + function doTests() 1.412 + { 1.413 + // Test focus events. 1.414 + gQueue = new eventQueue(); 1.415 + 1.416 + //////////////////////////////////////////////////////////////////////////// 1.417 + // tree popup autocomplete tests 1.418 + queueAutoCompleteTests("autocomplete"); 1.419 + 1.420 + //////////////////////////////////////////////////////////////////////////// 1.421 + // richlistbox popup autocomplete tests 1.422 + queueAutoCompleteTests("richautocomplete"); 1.423 + 1.424 + //////////////////////////////////////////////////////////////////////////// 1.425 + // HTML form autocomplete tests 1.426 + queueAutoCompleteTests(getIFrameDOMDoc("iframe").getElementById("input")); 1.427 + 1.428 + //////////////////////////////////////////////////////////////////////////// 1.429 + // HTML5 list autocomplete tests 1.430 + queueAutoCompleteTests(getIFrameDOMDoc("iframe2").getElementById("input")); 1.431 + 1.432 + //////////////////////////////////////////////////////////////////////////// 1.433 + // searchbar tests 1.434 + 1.435 + // focus searchbar, focus on text entry 1.436 + gQueue.push(new synthFocus("searchbar", 1.437 + new focusChecker(getTextEntry, "searchbar"))); 1.438 + // open search engine popup, no focus 1.439 + gQueue.push(new synthOpenComboboxKey("searchbar", new nofocusChecker())); 1.440 + // select first item, focus on it 1.441 + gQueue.push(new synthDownKey("searchbar", 1.442 + new focusChecker(getItem, new itemObj("searchbar", 0)))); 1.443 + // mouse over on second item, focus on it 1.444 + gQueue.push(new focusOnMouseOver(getItem, new itemObj("searchbar", 1))); 1.445 + // press enter key, focus on text entry 1.446 + gQueue.push(new synthEnterKey("searchbar", 1.447 + new focusChecker(getTextEntry, "searchbar"))); 1.448 + // click on search button, open popup, focus goes to document 1.449 + var searchBtn = getAccessible(getNode("searchbar").searchButton); 1.450 + gQueue.push(new synthClick(searchBtn, new focusChecker(document))); 1.451 + // select first item, focus on it 1.452 + gQueue.push(new synthDownKey("searchbar", 1.453 + new focusChecker(getItem, new itemObj("searchbar", 0)))); 1.454 + // close popup, focus goes on document 1.455 + gQueue.push(new synthEscapeKey("searchbar", new focusChecker(document))); 1.456 + 1.457 + gQueue.onFinish = function() 1.458 + { 1.459 + // unregister 'test-a11y-search' autocomplete search 1.460 + shutdownAutoComplete(); 1.461 + } 1.462 + gQueue.invoke(); // Will call SimpleTest.finish(); 1.463 + } 1.464 + 1.465 + SimpleTest.waitForExplicitFinish(); 1.466 + 1.467 + // Register 'test-a11y-search' autocomplete search. 1.468 + // XPFE AutoComplete needs to register early. 1.469 + initAutoComplete([ "hello", "hi" ], 1.470 + [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]); 1.471 + 1.472 + addA11yLoadEvent(initTests); 1.473 + ]]> 1.474 + </script> 1.475 + 1.476 + <hbox flex="1" style="overflow: auto;"> 1.477 + <body xmlns="http://www.w3.org/1999/xhtml"> 1.478 + <a target="_blank" 1.479 + href="https://bugzilla.mozilla.org/show_bug.cgi?id=383759" 1.480 + title="Focus event inconsistent for search box autocomplete"> 1.481 + Mozilla Bug 383759 1.482 + </a> 1.483 + <a target="_blank" 1.484 + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" 1.485 + title="Rework accessible focus handling"> 1.486 + Mozilla Bug 673958 1.487 + </a> 1.488 + <a target="_blank" 1.489 + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559766" 1.490 + title="Add accessibility support for @list on HTML input and for HTML datalist"> 1.491 + Mozilla Bug 559766 1.492 + </a> 1.493 + <p id="display"></p> 1.494 + <div id="content" style="display: none"></div> 1.495 + <pre id="test"> 1.496 + </pre> 1.497 + </body> 1.498 + 1.499 + <vbox flex="1"> 1.500 + <textbox id="autocomplete" type="autocomplete" 1.501 + autocompletesearch="test-a11y-search"/> 1.502 + 1.503 + <textbox id="richautocomplete" type="autocomplete" 1.504 + autocompletesearch="test-a11y-search" 1.505 + autocompletepopup="richpopup"/> 1.506 + <panel id="richpopup" type="autocomplete-richlistbox" noautofocus="true"/> 1.507 + 1.508 + <iframe id="iframe"/> 1.509 + 1.510 + <iframe id="iframe2"/> 1.511 + 1.512 + <searchbar id="searchbar"/> 1.513 + 1.514 + <vbox id="eventdump"/> 1.515 + </vbox> 1.516 + </hbox> 1.517 +</window>