tor-browser

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

test_mutation.html (20148B)


      1 <html>
      2 
      3 <head>
      4  <title>Accessible mutation events testing</title>
      5 
      6  <link rel="stylesheet" type="text/css"
      7        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
      8 
      9  <style>
     10    div.displayNone a { display:none; }
     11    div.visibilityHidden a { visibility:hidden; }
     12 </style>
     13 
     14  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
     15  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
     16 
     17  <script type="application/javascript"
     18          src="../common.js"></script>
     19  <script type="application/javascript"
     20          src="../events.js"></script>
     21 
     22  <script type="application/javascript">
     23    /**
     24     * Invokers.
     25     */
     26    var kNoEvents = 0;
     27 
     28    var kShowEvent = 1;
     29    var kHideEvent = 2;
     30    var kReorderEvent = 4;
     31    var kShowEvents = kShowEvent | kReorderEvent;
     32    var kHideEvents = kHideEvent | kReorderEvent;
     33    var kHideAndShowEvents = kHideEvents | kShowEvent;
     34 
     35    /**
     36     * Base class to test mutation a11y events.
     37     *
     38     * @param aNodeOrID          [in] node invoker's action is executed for
     39     * @param aEventTypes        [in] events to register (see constants above)
     40     * @param aDoNotExpectEvents [in] boolean indicates if events are expected
     41     */
     42    function mutateA11yTree(aNodeOrID, aEventTypes, aDoNotExpectEvents) {
     43      // Interface
     44      this.DOMNode = getNode(aNodeOrID);
     45      this.doNotExpectEvents = aDoNotExpectEvents;
     46      this.eventSeq = [];
     47      this.unexpectedEventSeq = [];
     48 
     49      /**
     50       * Change default target (aNodeOrID) registered for the given event type.
     51       */
     52      this.setTarget = function mutateA11yTree_setTarget(aEventType, aTarget) {
     53        var type = this.getA11yEventType(aEventType);
     54        for (var idx = 0; idx < this.getEventSeq().length; idx++) {
     55          if (this.getEventSeq()[idx].type == type) {
     56            this.getEventSeq()[idx].target = aTarget;
     57            return idx;
     58          }
     59        }
     60        return -1;
     61      };
     62 
     63      /**
     64       * Replace the default target currently registered for a given event type
     65       * with the nodes in the targets array.
     66       */
     67      this.setTargets = function mutateA11yTree_setTargets(aEventType, aTargets) {
     68        var targetIdx = this.setTarget(aEventType, aTargets[0]);
     69 
     70        var type = this.getA11yEventType(aEventType);
     71        for (var i = 1; i < aTargets.length; i++) {
     72          let checker = new invokerChecker(type, aTargets[i]);
     73          this.getEventSeq().splice(++targetIdx, 0, checker);
     74        }
     75      };
     76 
     77      // Implementation
     78      this.getA11yEventType = function mutateA11yTree_getA11yEventType(aEventType) {
     79        if (aEventType == kReorderEvent)
     80          return nsIAccessibleEvent.EVENT_REORDER;
     81 
     82        if (aEventType == kHideEvent)
     83          return nsIAccessibleEvent.EVENT_HIDE;
     84 
     85        if (aEventType == kShowEvent)
     86          return nsIAccessibleEvent.EVENT_SHOW;
     87 
     88        return 0;
     89      };
     90 
     91      this.getEventSeq = function mutateA11yTree_getEventSeq() {
     92        return this.doNotExpectEvents ? this.unexpectedEventSeq : this.eventSeq;
     93      };
     94 
     95      if (aEventTypes & kHideEvent) {
     96        let checker = new invokerChecker(this.getA11yEventType(kHideEvent),
     97                                         this.DOMNode);
     98        this.getEventSeq().push(checker);
     99      }
    100 
    101      if (aEventTypes & kShowEvent) {
    102        let checker = new invokerChecker(this.getA11yEventType(kShowEvent),
    103                                         this.DOMNode);
    104        this.getEventSeq().push(checker);
    105      }
    106 
    107      if (aEventTypes & kReorderEvent) {
    108        let checker = new invokerChecker(this.getA11yEventType(kReorderEvent),
    109                                         this.DOMNode.parentNode);
    110        this.getEventSeq().push(checker);
    111      }
    112    }
    113 
    114    /**
    115     * Change CSS style for the given node.
    116     */
    117    function changeStyle(aNodeOrID, aProp, aValue, aEventTypes) {
    118      this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false);
    119 
    120      this.invoke = function changeStyle_invoke() {
    121        this.DOMNode.style[aProp] = aValue;
    122      };
    123 
    124      this.getID = function changeStyle_getID() {
    125        return aNodeOrID + " change style " + aProp + " on value " + aValue;
    126      };
    127    }
    128 
    129    /**
    130     * Change class name for the given node.
    131     */
    132    function changeClass(aParentNodeOrID, aNodeOrID, aClassName, aEventTypes) {
    133      this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false);
    134 
    135      this.invoke = function changeClass_invoke() {
    136        this.parentDOMNode.className = aClassName;
    137      };
    138 
    139      this.getID = function changeClass_getID() {
    140        return aNodeOrID + " change class " + aClassName;
    141      };
    142 
    143      this.parentDOMNode = getNode(aParentNodeOrID);
    144    }
    145 
    146    /**
    147     * Clone the node and append it to its parent.
    148     */
    149    function cloneAndAppendToDOM(aNodeOrID, aEventTypes,
    150                                 aTargetsFunc, aReorderTargetFunc) {
    151      var eventTypes = aEventTypes || kShowEvents;
    152      var doNotExpectEvents = (aEventTypes == kNoEvents);
    153 
    154      this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes,
    155                                          doNotExpectEvents);
    156 
    157      this.invoke = function cloneAndAppendToDOM_invoke() {
    158        var newElm = this.DOMNode.cloneNode(true);
    159        newElm.removeAttribute("id");
    160 
    161        var targets = aTargetsFunc ?
    162          aTargetsFunc(newElm) : [newElm];
    163        this.setTargets(kShowEvent, targets);
    164 
    165        if (aReorderTargetFunc) {
    166          var reorderTarget = aReorderTargetFunc(this.DOMNode);
    167          this.setTarget(kReorderEvent, reorderTarget);
    168        }
    169 
    170        this.DOMNode.parentNode.appendChild(newElm);
    171      };
    172 
    173      this.getID = function cloneAndAppendToDOM_getID() {
    174        return aNodeOrID + " clone and append to DOM.";
    175      };
    176    }
    177 
    178    /**
    179     * Removes the node from DOM.
    180     */
    181    function removeFromDOM(aNodeOrID, aEventTypes,
    182                           aTargetsFunc, aReorderTargetFunc) {
    183      var eventTypes = aEventTypes || kHideEvents;
    184      var doNotExpectEvents = (aEventTypes == kNoEvents);
    185 
    186      this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes,
    187                                          doNotExpectEvents);
    188 
    189      this.invoke = function removeFromDOM_invoke() {
    190        this.DOMNode.remove();
    191      };
    192 
    193      this.getID = function removeFromDOM_getID() {
    194        return prettyName(aNodeOrID) + " remove from DOM.";
    195      };
    196 
    197      if (aTargetsFunc && (eventTypes & kHideEvent))
    198        this.setTargets(kHideEvent, aTargetsFunc(this.DOMNode));
    199 
    200      if (aReorderTargetFunc && (eventTypes & kReorderEvent))
    201        this.setTarget(kReorderEvent, aReorderTargetFunc(this.DOMNode));
    202    }
    203 
    204    /**
    205     * Clone the node and replace the original node by cloned one.
    206     */
    207    function cloneAndReplaceInDOM(aNodeOrID) {
    208      this.__proto__ = new mutateA11yTree(aNodeOrID, kHideAndShowEvents,
    209                                          false);
    210 
    211      this.invoke = function cloneAndReplaceInDOM_invoke() {
    212        this.DOMNode.parentNode.replaceChild(this.newElm, this.DOMNode);
    213      };
    214 
    215      this.getID = function cloneAndReplaceInDOM_getID() {
    216        return aNodeOrID + " clone and replace in DOM.";
    217      };
    218 
    219      this.newElm = this.DOMNode.cloneNode(true);
    220      this.newElm.removeAttribute("id");
    221      this.setTarget(kShowEvent, this.newElm);
    222    }
    223 
    224    /**
    225     * Trigger content insertion (flush layout), removal and insertion of
    226     * the same element for the same parent.
    227     */
    228    function test1(aContainerID) {
    229      this.divNode = document.createElement("div");
    230      this.divNode.setAttribute("id", "div-test1");
    231      this.containerNode = getNode(aContainerID);
    232 
    233      this.eventSeq = [
    234        new invokerChecker(EVENT_SHOW, this.divNode),
    235        new invokerChecker(EVENT_REORDER, this.containerNode),
    236      ];
    237 
    238      this.invoke = function test1_invoke() {
    239        this.containerNode.appendChild(this.divNode);
    240        getComputedStyle(this.divNode, "").color;
    241        this.containerNode.removeChild(this.divNode);
    242        this.containerNode.appendChild(this.divNode);
    243      };
    244 
    245      this.getID = function test1_getID() {
    246        return "fuzzy test #1: content insertion (flush layout), removal and" +
    247          "reinsertion";
    248      };
    249    }
    250 
    251    /**
    252     * Trigger content insertion (flush layout), removal and insertion of
    253     * the same element for the different parents.
    254     */
    255    function test2(aContainerID, aTmpContainerID) {
    256      this.divNode = document.createElement("div");
    257      this.divNode.setAttribute("id", "div-test2");
    258      this.containerNode = getNode(aContainerID);
    259      this.tmpContainerNode = getNode(aTmpContainerID);
    260      this.container = getAccessible(this.containerNode);
    261      this.tmpContainer = getAccessible(this.tmpContainerNode);
    262 
    263      this.eventSeq = [
    264        new invokerChecker(EVENT_SHOW, this.divNode),
    265        new invokerChecker(EVENT_REORDER, this.containerNode),
    266      ];
    267 
    268      this.unexpectedEventSeq = [
    269        new invokerChecker(EVENT_REORDER, this.tmpContainerNode),
    270      ];
    271 
    272      this.invoke = function test2_invoke() {
    273        this.tmpContainerNode.appendChild(this.divNode);
    274        getComputedStyle(this.divNode, "").color;
    275        this.tmpContainerNode.removeChild(this.divNode);
    276        this.containerNode.appendChild(this.divNode);
    277      };
    278 
    279      this.getID = function test2_getID() {
    280        return "fuzzy test #2: content insertion (flush layout), removal and" +
    281          "reinsertion under another container";
    282      };
    283    }
    284 
    285    /**
    286     * Content insertion (flush layout) and then removal (nothing was changed).
    287     */
    288    function test3(aContainerID) {
    289      this.divNode = document.createElement("div");
    290      this.divNode.setAttribute("id", "div-test3");
    291      this.containerNode = getNode(aContainerID);
    292 
    293      this.unexpectedEventSeq = [
    294        new invokerChecker(EVENT_SHOW, this.divNode),
    295        new invokerChecker(EVENT_HIDE, this.divNode),
    296        new invokerChecker(EVENT_REORDER, this.containerNode),
    297      ];
    298 
    299      this.invoke = function test3_invoke() {
    300        this.containerNode.appendChild(this.divNode);
    301        getComputedStyle(this.divNode, "").color;
    302        this.containerNode.removeChild(this.divNode);
    303      };
    304 
    305      this.getID = function test3_getID() {
    306        return "fuzzy test #3: content insertion (flush layout) and removal";
    307      };
    308    }
    309 
    310    function insertReferredElm(aContainerID) {
    311      this.containerNode = getNode(aContainerID);
    312 
    313      this.eventSeq = [
    314        new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.firstChild; }, this.containerNode),
    315        new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode),
    316        new invokerChecker(EVENT_REORDER, this.containerNode),
    317      ];
    318 
    319      this.invoke = function insertReferredElm_invoke() {
    320        let span = document.createElement("span");
    321        span.setAttribute("id", "insertReferredElms_span");
    322        let input = document.createElement("input");
    323        input.setAttribute("aria-labelledby", "insertReferredElms_span");
    324        this.containerNode.appendChild(span);
    325        this.containerNode.appendChild(input);
    326      };
    327 
    328      this.getID = function insertReferredElm_getID() {
    329        return "insert inaccessible element and then insert referring element to make it accessible";
    330      };
    331    }
    332 
    333    function showHiddenParentOfVisibleChild() {
    334      this.eventSeq = [
    335        new invokerChecker(EVENT_HIDE, getNode("c4_child")),
    336        new invokerChecker(EVENT_SHOW, getNode("c4_middle")),
    337        new invokerChecker(EVENT_REORDER, getNode("c4")),
    338      ];
    339 
    340      this.invoke = function showHiddenParentOfVisibleChild_invoke() {
    341        getNode("c4_middle").style.visibility = "visible";
    342      };
    343 
    344      this.getID = function showHiddenParentOfVisibleChild_getID() {
    345        return "show hidden parent of visible child";
    346      };
    347    }
    348 
    349    function hideNDestroyDoc() {
    350      this.txt = null;
    351      this.eventSeq = [
    352        new invokerChecker(EVENT_HIDE, () => { return this.txt; }),
    353      ];
    354 
    355      this.invoke = function hideNDestroyDoc_invoke() {
    356        this.txt = getAccessible("c5").firstChild.firstChild;
    357        this.txt.DOMNode.remove();
    358      };
    359 
    360      this.check = function hideNDestroyDoc_check() {
    361        getNode("c5").remove();
    362      };
    363 
    364      this.getID = function hideNDestroyDoc_getID() {
    365        return "remove text node and destroy a document on hide event";
    366      };
    367    }
    368 
    369    function hideHideNDestroyDoc() {
    370      this.target = null;
    371      this.eventSeq = [
    372        new invokerChecker(EVENT_HIDE, () => { return this.target; }),
    373      ];
    374 
    375      this.invoke = function hideHideNDestroyDoc_invoke() {
    376        var doc = getAccessible("c6").firstChild;
    377        var l1 = doc.firstChild;
    378        this.target = l1.firstChild;
    379        var l2 = doc.lastChild;
    380        l1.DOMNode.firstChild.remove();
    381        l2.DOMNode.firstChild.remove();
    382      };
    383 
    384      this.check = function hideHideNDestroyDoc_check() {
    385        getNode("c6").remove();
    386      };
    387 
    388      this.getID = function hideHideNDestroyDoc_getID() {
    389        return "remove text nodes (2 events in the queue) and destroy a document on first hide event";
    390      };
    391    }
    392 
    393    /**
    394     * Target getters.
    395     */
    396    function getFirstChild(aNode) {
    397      return [aNode.firstChild];
    398    }
    399    function getLastChild(aNode) {
    400      return [aNode.lastChild];
    401    }
    402 
    403    function getNEnsureFirstChild(aNode) {
    404      var node = aNode.firstChild;
    405      getAccessible(node);
    406      return [node];
    407    }
    408 
    409    function getNEnsureChildren(aNode) {
    410      var children = [];
    411      var node = aNode.firstChild;
    412      do {
    413        children.push(node);
    414        getAccessible(node);
    415        node = node.nextSibling;
    416      } while (node);
    417 
    418      return children;
    419    }
    420 
    421    function getParent(aNode) {
    422      return aNode.parentNode;
    423    }
    424 
    425    // gA11yEventDumpToConsole = true; // debug stuff
    426    // enableLogging("events,verbose");
    427 
    428    /**
    429     * Do tests.
    430     */
    431    var gQueue = null;
    432 
    433    function doTests() {
    434      gQueue = new eventQueue();
    435 
    436      // Show/hide events by changing of display style of accessible DOM node
    437      // from 'inline' to 'none', 'none' to 'inline'.
    438      let id = "link1";
    439      getAccessible(id); // ensure accessible is created
    440      gQueue.push(new changeStyle(id, "display", "none", kHideEvents));
    441      gQueue.push(new changeStyle(id, "display", "inline", kShowEvents));
    442 
    443      // Show/hide events by changing of visibility style of accessible DOM node
    444      // from 'visible' to 'hidden', 'hidden' to 'visible'.
    445      id = "link2";
    446      getAccessible(id);
    447      gQueue.push(new changeStyle(id, "visibility", "hidden", kHideEvents));
    448      gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents));
    449 
    450      // Show/hide events by changing of visibility style of accessible DOM node
    451      // from 'collapse' to 'visible', 'visible' to 'collapse'.
    452      id = "link4";
    453      gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents));
    454      gQueue.push(new changeStyle(id, "visibility", "collapse", kHideEvents));
    455 
    456      // Show/hide events by adding new accessible DOM node and removing old one.
    457      id = "link5";
    458      gQueue.push(new cloneAndAppendToDOM(id));
    459      gQueue.push(new removeFromDOM(id));
    460 
    461      // No show/hide events by adding new not accessible DOM node and removing
    462      // old one, no reorder event for their parent.
    463      id = "child1";
    464      gQueue.push(new cloneAndAppendToDOM(id, kNoEvents));
    465      gQueue.push(new removeFromDOM(id, kNoEvents));
    466 
    467      // Show/hide events by adding new accessible DOM node and removing
    468      // old one, there is reorder event for their parent.
    469      id = "child2";
    470      gQueue.push(new cloneAndAppendToDOM(id));
    471      gQueue.push(new removeFromDOM(id));
    472 
    473      // Show/hide events by adding new DOM node containing accessible DOM and
    474      // removing old one, there is reorder event for their parent.
    475      id = "child3";
    476      gQueue.push(new cloneAndAppendToDOM(id, kShowEvents, getFirstChild,
    477                                          getParent));
    478 
    479      // Hide event for accessible child of unaccessible removed DOM node and
    480      // reorder event for its parent.
    481      gQueue.push(new removeFromDOM(id, kHideEvents,
    482                                    getNEnsureFirstChild, getParent));
    483 
    484      // Hide events for accessible children of unaccessible removed DOM node
    485      // and reorder event for its parent.
    486      gQueue.push(new removeFromDOM("child4", kHideEvents,
    487                                    getNEnsureChildren, getParent));
    488 
    489      // Show/hide events by creating new accessible DOM node and replacing
    490      // old one.
    491      getAccessible("link6"); // ensure accessible is created
    492      gQueue.push(new cloneAndReplaceInDOM("link6"));
    493 
    494      // Show/hide events by changing class name on the parent node.
    495      gQueue.push(new changeClass("container2", "link7", "", kShowEvents));
    496      gQueue.push(new changeClass("container2", "link7", "displayNone",
    497                                  kHideEvents));
    498 
    499      gQueue.push(new changeClass("container3", "link8", "", kShowEvents));
    500      gQueue.push(new changeClass("container3", "link8", "visibilityHidden",
    501                                  kHideEvents));
    502 
    503      gQueue.push(new test1("testContainer"));
    504      gQueue.push(new test2("testContainer", "testContainer2"));
    505      gQueue.push(new test2("testContainer", "testNestedContainer"));
    506      gQueue.push(new test3("testContainer"));
    507      gQueue.push(new insertReferredElm("testContainer3"));
    508      gQueue.push(new showHiddenParentOfVisibleChild());
    509 
    510      gQueue.push(new hideNDestroyDoc());
    511      gQueue.push(new hideHideNDestroyDoc());
    512      gQueue.invoke(); // Will call SimpleTest.finish();
    513    }
    514 
    515    SimpleTest.waitForExplicitFinish();
    516    addA11yLoadEvent(doTests);
    517  </script>
    518 </head>
    519 
    520 <body>
    521 
    522  <a target="_blank"
    523     href="https://bugzilla.mozilla.org/show_bug.cgi?id=469985"
    524     title=" turn the test from bug 354745 into mochitest">
    525    Mozilla Bug 469985</a>
    526  <a target="_blank"
    527     href="https://bugzilla.mozilla.org/show_bug.cgi?id=472662"
    528     title="no reorder event when html:link display property is changed from 'none' to 'inline'">
    529    Mozilla Bug 472662</a>
    530  <a target="_blank"
    531     title="Rework accessible tree update code"
    532     href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
    533    Mozilla Bug 570275</a>
    534  <a target="_blank"
    535     title="Develop a way to handle visibility style"
    536     href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125">
    537    Mozilla Bug 606125</a>
    538  <a target="_blank"
    539     title="Update accessible tree on content insertion after layout"
    540     href="https://bugzilla.mozilla.org/show_bug.cgi?id=498015">
    541    Mozilla Bug 498015</a>
    542 
    543  <p id="display"></p>
    544  <div id="content" style="display: none"></div>
    545  <pre id="test">
    546  </pre>
    547  <div id="eventdump"></div>
    548 
    549  <div id="testContainer">
    550    <a id="link1" href="http://www.google.com">Link #1</a>
    551    <a id="link2" href="http://www.google.com">Link #2</a>
    552    <a id="link3" href="http://www.google.com">Link #3</a>
    553    <a id="link4" href="http://www.google.com" style="visibility:collapse">Link #4</a>
    554    <a id="link5" href="http://www.google.com">Link #5</a>
    555 
    556    <div id="container" role="list">
    557      <span id="child1"></span>
    558      <span id="child2" role="listitem"></span>
    559      <span id="child3"><span role="listitem"></span></span>
    560      <span id="child4"><span id="child4_1" role="listitem"></span><span id="child4_2" role="listitem"></span></span>
    561    </div>
    562 
    563    <a id="link6" href="http://www.google.com">Link #6</a>
    564 
    565    <div id="container2" class="displayNone"><a id="link7">Link #7</a></div>
    566    <div id="container3" class="visibilityHidden"><a id="link8">Link #8</a></div>
    567    <div id="testNestedContainer"></div>
    568  </div>
    569  <div id="testContainer2"></div>
    570  <div id="testContainer3"></div>
    571 
    572  <div id="c4">
    573    <div style="visibility:hidden" id="c4_middle">
    574    <div style="visibility:visible" id="c4_child"></div>
    575  </div>
    576 
    577  <iframe id="c5" src="data:text/html,hey"></iframe>
    578  <iframe id="c6" src="data:text/html,<label>l</label><label>l</label>"></iframe>
    579 </body>
    580 </html>