summaryrefslogtreecommitdiffstats
path: root/accessible/tests/mochitest/events/test_focus_autocomplete.xul
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/tests/mochitest/events/test_focus_autocomplete.xul')
-rw-r--r--accessible/tests/mochitest/events/test_focus_autocomplete.xul518
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>