tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>