test_focus_autocomplete.xhtml (16638B)
1 <?xml version="1.0"?> 2 <?xml-stylesheet href="chrome://global/skin" type="text/css"?> 3 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" 4 type="text/css"?> 5 6 <!-- SeaMonkey searchbar --> 7 <?xml-stylesheet href="chrome://navigator/content/navigator.css" 8 type="text/css"?> 9 10 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 11 xmlns:html="http://www.w3.org/1999/xhtml" 12 title="Accessible focus event testing"> 13 14 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> 15 <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> 16 17 <script type="application/javascript" 18 src="../common.js" /> 19 <script type="application/javascript" 20 src="../role.js" /> 21 <script type="application/javascript" 22 src="../states.js" /> 23 <script type="application/javascript" 24 src="../events.js" /> 25 26 <script type="application/javascript" 27 src="../autocomplete.js" /> 28 29 <script type="application/javascript"> 30 <![CDATA[ 31 //////////////////////////////////////////////////////////////////////////// 32 // Invokers 33 34 function loadFormAutoComplete(aIFrameID) 35 { 36 this.iframeNode = getNode(aIFrameID); 37 this.iframe = getAccessible(aIFrameID); 38 39 this.eventSeq = [ 40 new invokerChecker(EVENT_REORDER, this.iframe) 41 ]; 42 43 this.invoke = function loadFormAutoComplete_invoke() 44 { 45 var url = "data:text/html,<html><body><form id='form'>" + 46 "<input id='input' name='a11ytest-formautocomplete'>" + 47 "</form></body></html>"; 48 this.iframeNode.setAttribute("src", url); 49 } 50 51 this.getID = function loadFormAutoComplete_getID() 52 { 53 return "load form autocomplete page"; 54 } 55 } 56 57 function initFormAutoCompleteBy(aIFrameID, aAutoCompleteValue) 58 { 59 this.iframe = getAccessible(aIFrameID); 60 61 this.eventSeq = [ 62 new invokerChecker(EVENT_REORDER, this.iframe) 63 ]; 64 65 this.invoke = function initFormAutoCompleteBy_invoke() 66 { 67 var iframeDOMDoc = getIFrameDOMDoc(aIFrameID); 68 69 var inputNode = iframeDOMDoc.getElementById("input"); 70 inputNode.value = aAutoCompleteValue; 71 var formNode = iframeDOMDoc.getElementById("form"); 72 formNode.submit(); 73 } 74 75 this.getID = function initFormAutoCompleteBy_getID() 76 { 77 return "init form autocomplete by '" + aAutoCompleteValue + "'"; 78 } 79 } 80 81 function loadHTML5ListAutoComplete(aIFrameID) 82 { 83 this.iframeNode = getNode(aIFrameID); 84 this.iframe = getAccessible(aIFrameID); 85 86 this.eventSeq = [ 87 new invokerChecker(EVENT_REORDER, this.iframe) 88 ]; 89 90 this.invoke = function loadHTML5ListAutoComplete_invoke() 91 { 92 var url = "data:text/html,<html><body>" + 93 "<datalist id='cities'><option>hello</option><option>hi</option></datalist>" + 94 "<input id='input' list='cities'>" + 95 "</body></html>"; 96 this.iframeNode.setAttribute("src", url); 97 } 98 99 this.getID = function loadHTML5ListAutoComplete_getID() 100 { 101 return "load HTML5 list autocomplete page"; 102 } 103 } 104 105 function removeChar(aID, aCheckerOrEventSeq) 106 { 107 this.__proto__ = new synthAction(aID, aCheckerOrEventSeq); 108 109 this.invoke = function removeChar_invoke() 110 { 111 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}); 112 synthesizeKey("KEY_Delete"); 113 } 114 115 this.getID = function removeChar_getID() 116 { 117 return "remove char on " + prettyName(aID); 118 } 119 } 120 121 function replaceOnChar(aID, aChar, aCheckerOrEventSeq) 122 { 123 this.__proto__ = new synthAction(aID, aCheckerOrEventSeq); 124 125 this.invoke = function replaceOnChar_invoke() 126 { 127 this.DOMNode.select(); 128 sendString(aChar); 129 } 130 131 this.getID = function replaceOnChar_getID() 132 { 133 return "replace on char '" + aChar + "' for" + prettyName(aID); 134 } 135 } 136 137 function focusOnMouseOver(aIDFunc, aIDFuncArg) 138 { 139 this.eventSeq = [ new focusChecker(aIDFunc, aIDFuncArg) ]; 140 141 this.invoke = function focusOnMouseOver_invoke() 142 { 143 this.id = aIDFunc(aIDFuncArg); 144 this.node = getNode(this.id); 145 this.window = this.node.ownerGlobal; 146 147 if (this.node.localName == "tree") { 148 var tree = getAccessible(this.node); 149 var accessible = getAccessible(this.id); 150 if (tree != accessible) { 151 var itemX = {}, itemY = {}, treeX = {}, treeY = {}; 152 accessible.getBounds(itemX, itemY, {}, {}); 153 tree.getBounds(treeX, treeY, {}, {}); 154 this.x = itemX.value - treeX.value; 155 this.y = itemY.value - treeY.value; 156 } 157 } 158 159 // Generate mouse move events in timeouts until autocomplete popup list 160 // doesn't have it, the reason is do that because autocomplete popup 161 // ignores mousemove events firing in too short range. 162 synthesizeMouse(this.node, this.x, this.y, { type: "mousemove" }); 163 this.doMouseMoveFlood(this); 164 } 165 166 this.finalCheck = function focusOnMouseOver_getID() 167 { 168 this.isFlooding = false; 169 } 170 171 this.getID = function focusOnMouseOver_getID() 172 { 173 return "mouse over on " + prettyName(aIDFunc(aIDFuncArg)); 174 } 175 176 this.doMouseMoveFlood = function focusOnMouseOver_doMouseMoveFlood(aThis) 177 { 178 synthesizeMouse(aThis.node, aThis.x + 1, aThis.y + 1, 179 { type: "mousemove" }, aThis.window); 180 181 if (aThis.isFlooding) 182 aThis.window.setTimeout(aThis.doMouseMoveFlood, 0, aThis); 183 } 184 185 this.id = null; 186 this.node = null; 187 this.window = null; 188 189 this.isFlooding = true; 190 this.x = 1; 191 this.y = 1; 192 } 193 194 function selectByClick(aIDFunc, aIDFuncArg, 195 aFocusTargetFunc, aFocusTargetFuncArg) 196 { 197 this.eventSeq = [ new focusChecker(aFocusTargetFunc, aFocusTargetFuncArg) ]; 198 199 this.invoke = function selectByClick_invoke() 200 { 201 var id = aIDFunc(aIDFuncArg); 202 var node = getNode(id); 203 var targetWindow = node.ownerGlobal; 204 205 if (node.localName == "tree") { 206 var tree = getAccessible(node); 207 var accessible = getAccessible(id); 208 if (tree != accessible) { 209 var itemX = {}, itemY = {}, treeX = {}, treeY = {}; 210 accessible.getBounds(itemX, itemY, {}, {}); 211 tree.getBounds(treeX, treeY, {}, {}); 212 this.x = itemX.value - treeX.value; 213 this.y = itemY.value - treeY.value; 214 } 215 } 216 217 synthesizeMouseAtCenter(node, {}, targetWindow); 218 } 219 220 this.getID = function selectByClick_getID() 221 { 222 return "select by click " + prettyName(aIDFunc(aIDFuncArg)); 223 } 224 } 225 226 //////////////////////////////////////////////////////////////////////////// 227 // Target getters 228 229 function getItem(aItemObj) 230 { 231 var autocompleteNode = aItemObj.autocompleteNode; 232 233 // XUL searchbar 234 if (autocompleteNode.localName == "searchbar") { 235 let popupNode = autocompleteNode._popup; 236 if (popupNode) { 237 let list = getAccessible(popupNode); 238 return list.getChildAt(aItemObj.index); 239 } 240 } 241 242 // XUL autocomplete 243 let popupNode = autocompleteNode.popup; 244 if (!popupNode) { 245 // HTML form autocomplete 246 var controller = Cc["@mozilla.org/autocomplete/controller;1"]. 247 getService(Ci.nsIAutoCompleteController); 248 popupNode = controller.input.popup; 249 } 250 251 if (popupNode) { 252 if ("richlistbox" in popupNode) { 253 let list = getAccessible(popupNode.richlistbox); 254 return list.getChildAt(aItemObj.index); 255 } 256 257 let list = getAccessible(popupNode.tree); 258 return list.getChildAt(aItemObj.index + 1); 259 } 260 return null; 261 } 262 263 function getTextEntry(aID) 264 { 265 // For form autocompletes the autocomplete widget and text entry widget 266 // is the same widget, for XUL autocompletes the text entry is a first 267 // child. 268 var localName = getNode(aID).localName; 269 270 // HTML form autocomplete 271 if (localName == "input") 272 return getAccessible(aID); 273 274 // XUL searchbar 275 if (localName == "searchbar") 276 return getAccessible(getNode(aID).textbox); 277 278 return null; 279 } 280 281 function itemObj(aID, aIdx) 282 { 283 this.autocompleteNode = getNode(aID); 284 285 this.autocomplete = this.autocompleteNode.localName == "searchbar" ? 286 getAccessible(this.autocompleteNode.textbox) : 287 getAccessible(this.autocompleteNode); 288 289 this.index = aIdx; 290 } 291 292 function getIFrameDOMDoc(aIFrameID) 293 { 294 return getNode(aIFrameID).contentDocument; 295 } 296 297 //////////////////////////////////////////////////////////////////////////// 298 // Test helpers 299 300 function queueAutoCompleteTests(aID) 301 { 302 // focus autocomplete text entry 303 gQueue.push(new synthFocus(aID, new focusChecker(getTextEntry, aID))); 304 305 // open autocomplete popup 306 gQueue.push(new synthDownKey(aID, new nofocusChecker())); 307 308 // select second option ('hi' option), focus on it 309 gQueue.push(new synthUpKey(aID, 310 new focusChecker(getItem, new itemObj(aID, 1)))); 311 312 // choose selected option, focus on text entry 313 gQueue.push(new synthEnterKey(aID, new focusChecker(getTextEntry, aID))); 314 315 // remove char, autocomplete popup appears 316 gQueue.push(new removeChar(aID, new nofocusChecker())); 317 318 // select first option ('hello' option), focus on it 319 gQueue.push(new synthDownKey(aID, 320 new focusChecker(getItem, new itemObj(aID, 0)))); 321 322 // mouse move on second option ('hi' option), focus on it 323 gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 1))); 324 325 // autocomplete popup updated (no selected item), focus on textentry 326 gQueue.push(new synthKey(aID, "e", null, new focusChecker(getTextEntry, aID))); 327 328 // select first option ('hello' option), focus on it 329 gQueue.push(new synthDownKey(aID, 330 new focusChecker(getItem, new itemObj(aID, 0)))); 331 332 // popup gets hidden, focus on textentry 333 gQueue.push(new synthRightKey(aID, new focusChecker(getTextEntry, aID))); 334 335 // popup gets open, no focus 336 gQueue.push(new synthOpenComboboxKey(aID, new nofocusChecker())); 337 338 // select first option again ('hello' option), focus on it 339 gQueue.push(new synthDownKey(aID, 340 new focusChecker(getItem, new itemObj(aID, 0)))); 341 342 // no option is selected, focus on text entry 343 gQueue.push(new synthUpKey(aID, new focusChecker(getTextEntry, aID))); 344 345 // close popup, no focus 346 gQueue.push(new synthEscapeKey(aID, new nofocusChecker())); 347 348 // autocomplete popup appears (no selected item), focus stays on textentry 349 gQueue.push(new replaceOnChar(aID, "h", new nofocusChecker())); 350 351 // mouse move on first option ('hello' option), focus on it 352 gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 0))); 353 354 // click first option ('hello' option), popup closes, focus on text entry 355 gQueue.push(new selectByClick(getItem, new itemObj(aID, 0), getTextEntry, aID)); 356 } 357 358 //////////////////////////////////////////////////////////////////////////// 359 // Tests 360 361 //gA11yEventDumpToConsole = true; // debug stuff 362 363 var gInitQueue = null; 364 function initTests() 365 { 366 if (SEAMONKEY || MAC) { 367 todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237), and on Mac (bug 746177)"); 368 shutdownAutoComplete(); 369 SimpleTest.finish(); 370 return; 371 } 372 373 gInitQueue = new eventQueue(); 374 gInitQueue.push(new loadFormAutoComplete("iframe")); 375 gInitQueue.push(new initFormAutoCompleteBy("iframe", "hello")); 376 gInitQueue.push(new initFormAutoCompleteBy("iframe", "hi")); 377 gInitQueue.push(new loadHTML5ListAutoComplete("iframe2")); 378 gInitQueue.onFinish = function initQueue_onFinish() 379 { 380 SimpleTest.executeSoon(doTests); 381 return DO_NOT_FINISH_TEST; 382 } 383 gInitQueue.invoke(); 384 } 385 386 var gQueue = null; 387 function doTests() 388 { 389 // Test focus events. 390 gQueue = new eventQueue(); 391 392 //////////////////////////////////////////////////////////////////////////// 393 // tree popup autocomplete tests 394 queueAutoCompleteTests("autocomplete"); 395 396 //////////////////////////////////////////////////////////////////////////// 397 // richlistbox popup autocomplete tests 398 queueAutoCompleteTests("richautocomplete"); 399 400 //////////////////////////////////////////////////////////////////////////// 401 // HTML form autocomplete tests 402 queueAutoCompleteTests(getIFrameDOMDoc("iframe").getElementById("input")); 403 404 //////////////////////////////////////////////////////////////////////////// 405 // HTML5 list autocomplete tests 406 queueAutoCompleteTests(getIFrameDOMDoc("iframe2").getElementById("input")); 407 408 //////////////////////////////////////////////////////////////////////////// 409 // searchbar tests 410 411 // focus searchbar, focus on text entry 412 gQueue.push(new synthFocus("searchbar", 413 new focusChecker(getTextEntry, "searchbar"))); 414 // open search engine popup, no focus 415 gQueue.push(new synthOpenComboboxKey("searchbar", new nofocusChecker())); 416 // select first item, focus on it 417 gQueue.push(new synthDownKey("searchbar", 418 new focusChecker(getItem, new itemObj("searchbar", 0)))); 419 // mouse over on second item, focus on it 420 gQueue.push(new focusOnMouseOver(getItem, new itemObj("searchbar", 1))); 421 // press enter key, focus on text entry 422 gQueue.push(new synthEnterKey("searchbar", 423 new focusChecker(getTextEntry, "searchbar"))); 424 // click on search button, open popup, focus goes to document 425 var searchBtn = getAccessible(getNode("searchbar").searchButton); 426 gQueue.push(new synthClick(searchBtn, new focusChecker(document))); 427 // select first item, focus on it 428 gQueue.push(new synthDownKey("searchbar", 429 new focusChecker(getItem, new itemObj("searchbar", 0)))); 430 // close popup, focus goes on document 431 gQueue.push(new synthEscapeKey("searchbar", new focusChecker(document))); 432 433 gQueue.onFinish = function() 434 { 435 // unregister 'test-a11y-search' autocomplete search 436 shutdownAutoComplete(); 437 } 438 gQueue.invoke(); // Will call SimpleTest.finish(); 439 } 440 441 SimpleTest.waitForExplicitFinish(); 442 443 // Register 'test-a11y-search' autocomplete search. 444 // XPFE AutoComplete needs to register early. 445 initAutoComplete([ "hello", "hi" ], 446 [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]); 447 448 addA11yLoadEvent(initTests); 449 ]]> 450 </script> 451 452 <hbox flex="1" style="overflow: auto;"> 453 <body xmlns="http://www.w3.org/1999/xhtml"> 454 <a target="_blank" 455 href="https://bugzilla.mozilla.org/show_bug.cgi?id=383759" 456 title="Focus event inconsistent for search box autocomplete"> 457 Mozilla Bug 383759 458 </a> 459 <a target="_blank" 460 href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" 461 title="Rework accessible focus handling"> 462 Mozilla Bug 673958 463 </a> 464 <a target="_blank" 465 href="https://bugzilla.mozilla.org/show_bug.cgi?id=559766" 466 title="Add accessibility support for @list on HTML input and for HTML datalist"> 467 Mozilla Bug 559766 468 </a> 469 <p id="display"></p> 470 <div id="content" style="display: none"></div> 471 <pre id="test"> 472 </pre> 473 </body> 474 475 <vbox flex="1"> 476 <html:input is="autocomplete-input" 477 id="autocomplete" 478 autocompletesearch="test-a11y-search"/> 479 480 <html:input is="autocomplete-input" 481 id="richautocomplete" 482 autocompletesearch="test-a11y-search" 483 autocompletepopup="richpopup"/> 484 <panel is="autocomplete-richlistbox-popup" 485 id="richpopup" 486 type="autocomplete-richlistbox" 487 noautofocus="true"/> 488 489 <iframe id="iframe"/> 490 491 <iframe id="iframe2"/> 492 493 <searchbar id="searchbar"/> 494 </vbox> 495 </hbox> 496 </window>