tor-browser

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

test_statechange.html (19622B)


      1 <html>
      2 
      3 <head>
      4  <title>Accessible state change event testing</title>
      5 
      6  <link rel="stylesheet" type="text/css"
      7        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
      8 
      9  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
     10 
     11  <script type="application/javascript"
     12          src="../common.js"></script>
     13  <script type="application/javascript"
     14          src="../promisified-events.js"></script>
     15  <script type="application/javascript"
     16          src="../role.js"></script>
     17  <script type="application/javascript"
     18          src="../states.js"></script>
     19 
     20  <script type="application/javascript">
     21    async function openNode(aIDDetails, aIDSummary, aIsOpen) {
     22      let p = waitForStateChange(aIDSummary, STATE_EXPANDED, aIsOpen, false);
     23      if (aIsOpen) {
     24        getNode(aIDDetails).setAttribute("open", "");
     25      } else {
     26        getNode(aIDDetails).removeAttribute("open");
     27      }
     28      await p;
     29    }
     30 
     31    async function makeEditableDoc(aDocNode) {
     32      let p = waitForStateChange(aDocNode, EXT_STATE_EDITABLE, true, true);
     33      aDocNode.designMode = "on";
     34      await p;
     35    }
     36 
     37    async function invalidInput(aNodeOrID) {
     38      let p = waitForStateChange(aNodeOrID, STATE_INVALID, true, false);
     39      getNode(aNodeOrID).value = "I am not an email";
     40      await p;
     41    }
     42 
     43    async function changeCheckInput(aID, aIsChecked) {
     44      let p = waitForStateChange(aID, STATE_CHECKED, aIsChecked, false);
     45      getNode(aID).checked = aIsChecked;
     46      await p;
     47    }
     48 
     49    async function changeRequiredState(aID, aIsRequired) {
     50      let p = waitForStateChange(aID, STATE_REQUIRED, aIsRequired, false);
     51      getNode(aID).required = aIsRequired;
     52      await p;
     53    }
     54 
     55    async function stateChangeOnFileInput(aID, aAttr, aValue,
     56                                    aState, aIsExtraState, aIsEnabled) {
     57      let fileControlNode = getNode(aID);
     58      let browseButton = getAccessible(fileControlNode);
     59      let p = waitForStateChange(
     60        browseButton, aState, aIsEnabled, aIsExtraState
     61      );
     62      fileControlNode.setAttribute(aAttr, aValue);
     63      await p;
     64    }
     65 
     66    function toggleSentinel() {
     67      let sentinel = getNode("sentinel");
     68      if (sentinel.hasAttribute("aria-busy"))  {
     69        sentinel.removeAttribute("aria-busy");
     70      } else {
     71        sentinel.setAttribute("aria-busy", "true");
     72      }
     73    }
     74 
     75    async function toggleStateChange(aID, aAttr, aState, aIsExtraState) {
     76      let p = waitForEvents([
     77        stateChangeEventArgs(aID, aState, true, aIsExtraState),
     78        [EVENT_STATE_CHANGE, "sentinel"]
     79        ]);
     80      getNode(aID).setAttribute(aAttr, "true");
     81      toggleSentinel();
     82      await p;
     83      p = waitForEvents([
     84        stateChangeEventArgs(aID, aState, false, aIsExtraState),
     85        [EVENT_STATE_CHANGE, "sentinel"]
     86        ]);
     87      getNode(aID).setAttribute(aAttr, "false");
     88      toggleSentinel();
     89      await p;
     90    }
     91 
     92    async function dupeStateChange(aID, aAttr, aValue,
     93                             aState, aIsExtraState, aIsEnabled) {
     94      let p = waitForEvents([
     95        stateChangeEventArgs(aID, aState, aIsEnabled, aIsExtraState),
     96        [EVENT_STATE_CHANGE, "sentinel"]
     97        ]);
     98      getNode(aID).setAttribute(aAttr, aValue);
     99      getNode(aID).setAttribute(aAttr, aValue);
    100      toggleSentinel();
    101      await p;
    102    }
    103 
    104    async function oppositeStateChange(aID, aAttr, aState, aIsExtraState) {
    105      let p = waitForEvents({
    106        expected: [[EVENT_STATE_CHANGE, "sentinel"]],
    107        unexpected: [
    108          stateChangeEventArgs(aID, aState, false, aIsExtraState),
    109          stateChangeEventArgs(aID, aState, true, aIsExtraState)
    110        ]
    111      });
    112      getNode(aID).setAttribute(aAttr, "false");
    113      getNode(aID).setAttribute(aAttr, "true");
    114      toggleSentinel();
    115      await p;
    116    }
    117 
    118    /**
    119     * Change concomitant ARIA and native attribute at once.
    120     */
    121    async function echoingStateChange(aID, aARIAAttr, aAttr, aValue,
    122                                      aState, aIsExtraState, aIsEnabled) {
    123      let p = waitForStateChange(aID, aState, aIsEnabled, aIsExtraState);
    124      if (aValue == null) {
    125        getNode(aID).removeAttribute(aARIAAttr);
    126        getNode(aID).removeAttribute(aAttr);
    127      } else {
    128        getNode(aID).setAttribute(aARIAAttr, aValue);
    129        getNode(aID).setAttribute(aAttr, aValue);
    130      }
    131      await p;
    132    }
    133 
    134    async function testHasPopup() {
    135      let p = waitForStateChange("popupButton", STATE_HASPOPUP, true, false);
    136      getNode("popupButton").setAttribute("aria-haspopup", "true");
    137      await p;
    138 
    139      p = waitForStateChange("popupButton", STATE_HASPOPUP, false, false);
    140      getNode("popupButton").setAttribute("aria-haspopup", "false");
    141      await p;
    142 
    143      p = waitForStateChange("popupButton", STATE_HASPOPUP, true, false);
    144      getNode("popupButton").setAttribute("aria-haspopup", "true");
    145      await p;
    146 
    147      p = waitForStateChange("popupButton", STATE_HASPOPUP, false, false);
    148      getNode("popupButton").removeAttribute("aria-haspopup");
    149      await p;
    150    }
    151 
    152    async function testDefaultSubmitChange() {
    153      testStates("default-button",
    154        STATE_DEFAULT, 0,
    155        0, 0,
    156        "button should have DEFAULT state");
    157      let button = document.createElement("button");
    158      button.textContent = "new default";
    159      let p = waitForStateChange("default-button", STATE_DEFAULT, false, false);
    160      getNode("default-button").before(button);
    161      await p;
    162      testStates("default-button",
    163        0, 0,
    164        STATE_DEFAULT, 0,
    165        "button should not have DEFAULT state");
    166      p = waitForStateChange("default-button", STATE_DEFAULT, true, false);
    167      button.remove();
    168      await p;
    169      testStates("default-button",
    170        STATE_DEFAULT, 0,
    171        0, 0,
    172        "button should have DEFAULT state");
    173    }
    174 
    175    async function testReadOnly() {
    176      let p = waitForStateChange("email", STATE_READONLY, true, false);
    177      getNode("email").setAttribute("readonly", "true");
    178      await p;
    179      p = waitForStateChange("email", STATE_READONLY, false, false);
    180      getNode("email").removeAttribute("readonly");
    181      await p;
    182    }
    183 
    184    async function testReadonlyUntilEditable() {
    185      testStates("article",
    186        STATE_READONLY, 0,
    187        0, EXT_STATE_EDITABLE,
    188        "article is READONLY and not EDITABLE");
    189      let p = waitForEvents([
    190        stateChangeEventArgs("article", STATE_READONLY, false, false),
    191        stateChangeEventArgs("article", EXT_STATE_EDITABLE, true, true)]);
    192      getNode("article").contentEditable = "true";
    193      await p;
    194      testStates("article",
    195        0, EXT_STATE_EDITABLE,
    196        STATE_READONLY, 0,
    197        "article is EDITABLE and not READONLY");
    198      p = waitForEvents([
    199        stateChangeEventArgs("article", STATE_READONLY, true, false),
    200        stateChangeEventArgs("article", EXT_STATE_EDITABLE, false, true)]);
    201      getNode("article").contentEditable = "false";
    202      await p;
    203      testStates("article",
    204        STATE_READONLY, 0,
    205        0, EXT_STATE_EDITABLE,
    206        "article is READONLY and not EDITABLE");
    207    }
    208 
    209    async function testAnimatedImage() {
    210      testStates("animated-image",
    211        STATE_ANIMATED, 0,
    212        0, 0,
    213        "image should be animated 1");
    214      let p = waitForStateChange("animated-image", STATE_ANIMATED, false, false);
    215      getNode("animated-image").src = "../animated-gif-finalframe.gif";
    216      await p;
    217      testStates("animated-image",
    218        0, 0,
    219        STATE_ANIMATED, 0,
    220        "image should not be animated 2");
    221      p = waitForStateChange("animated-image", STATE_ANIMATED, true, false);
    222      getNode("animated-image").src = "../animated-gif.gif";
    223      await p;
    224      testStates("animated-image",
    225        STATE_ANIMATED, 0,
    226        0, 0,
    227        "image should be animated 3");
    228    }
    229 
    230    async function testImageLoad() {
    231      let img = document.createElement("img");
    232      img.id = "image";
    233      // eslint-disable-next-line @microsoft/sdl/no-insecure-url
    234      img.src = "http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs";
    235      let p = waitForEvent(EVENT_SHOW, "image");
    236      getNode("eventdump").before(img);
    237      await p;
    238      testStates("image",
    239        STATE_INVISIBLE, 0,
    240        0, 0,
    241        "image should be invisible");
    242      p = waitForStateChange("image", STATE_INVISIBLE, false, false);
    243      // eslint-disable-next-line @microsoft/sdl/no-insecure-url
    244      await fetch("http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs?complete");
    245      await p;
    246      testStates("image",
    247        0, 0,
    248        STATE_INVISIBLE, 0,
    249        "image should be invisible");
    250    }
    251 
    252    async function testMultiSelectable(aID, aAttribute) {
    253      testStates(aID,
    254        0, 0,
    255        STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0,
    256        `${aID} should not be multiselectable`);
    257      let p = waitForEvents([
    258        stateChangeEventArgs(aID, STATE_MULTISELECTABLE, true, false),
    259        stateChangeEventArgs(aID, STATE_EXTSELECTABLE, true, false),
    260      ]);
    261      getNode(aID).setAttribute(aAttribute, true);
    262      await p;
    263      testStates(aID,
    264        STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0,
    265        0, 0,
    266        `${aID} should not be multiselectable`);
    267      p = waitForEvents([
    268        stateChangeEventArgs(aID, STATE_MULTISELECTABLE, false, false),
    269        stateChangeEventArgs(aID, STATE_EXTSELECTABLE, false, false),
    270      ]);
    271      getNode(aID).removeAttribute(aAttribute);
    272      await p;
    273      testStates(aID,
    274        0, 0,
    275        STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0,
    276        `${aID} should not be multiselectable`);
    277    }
    278 
    279    async function testAutocomplete() {
    280      // A text input will have autocomplete via browser's form autofill...
    281      testStates("input",
    282        0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
    283        0, 0,
    284        "input supports autocompletion");
    285      // unless it is explicitly turned off.
    286      testStates("input-autocomplete-off",
    287        0, 0,
    288        0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
    289        "input-autocomplete-off does not support autocompletion");
    290      // An input with a datalist will always have autocomplete.
    291      testStates("input-list",
    292        0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
    293        0, 0,
    294        "input-list supports autocompletion");
    295      // password fields don't get autocomplete.
    296      testStates("input-password",
    297        0, 0,
    298        0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
    299        "input-autocomplete-off does not support autocompletion");
    300 
    301      let p = waitForEvents({
    302        expected: [
    303          // Setting the form's autocomplete attribute to "off" will cause
    304          // "input" to lost its autocomplete state.
    305          stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, false, true)
    306        ],
    307        unexpected: [
    308          // "input-list" should preserve its autocomplete state regardless of
    309          // forms "autocomplete" attribute
    310          [EVENT_STATE_CHANGE, "input-list"],
    311          // "input-autocomplete-off" already has its autocomplte off, so no state
    312          // change here.
    313          [EVENT_STATE_CHANGE, "input-autocomplete-off"],
    314          // passwords never get autocomplete
    315          [EVENT_STATE_CHANGE, "input-password"],
    316        ]
    317      });
    318 
    319      getNode("form").setAttribute("autocomplete", "off");
    320 
    321      await p;
    322 
    323      // Same when we remove the form's autocomplete attribute.
    324      p = waitForEvents({
    325        expected: [stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, true, true)],
    326        unexpected: [
    327          [EVENT_STATE_CHANGE, "input-list"],
    328          [EVENT_STATE_CHANGE, "input-autocomplete-off"],
    329          [EVENT_STATE_CHANGE, "input-password"],
    330        ]
    331      });
    332 
    333      getNode("form").removeAttribute("autocomplete");
    334 
    335      await p;
    336 
    337      p = waitForEvents({
    338        expected: [
    339          // Forcing autocomplete off on an input will cause a state change
    340          stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, false, true),
    341          // Associating a datalist with an autocomplete=off input
    342          // will give it an autocomplete state, regardless.
    343          stateChangeEventArgs("input-autocomplete-off", EXT_STATE_SUPPORTS_AUTOCOMPLETION, true, true),
    344          // XXX: datalist inputs also get a HASPOPUP state, the inconsistent
    345          // use of that state is inexplicable, but lets make sure we fire state
    346          // change events for it anyway.
    347          stateChangeEventArgs("input-autocomplete-off", STATE_HASPOPUP, true, false),
    348        ],
    349        unexpected: [
    350          // Forcing autocomplete off with a dataset input does nothing.
    351          [EVENT_STATE_CHANGE, "input-list"],
    352          // passwords never get autocomplete
    353          [EVENT_STATE_CHANGE, "input-password"],
    354        ]
    355      });
    356 
    357      getNode("input").setAttribute("autocomplete", "off");
    358      getNode("input-list").setAttribute("autocomplete", "off");
    359      getNode("input-autocomplete-off").setAttribute("list", "browsers");
    360      getNode("input-password").setAttribute("autocomplete", "off");
    361 
    362      await p;
    363    }
    364 
    365    async function doTests() {
    366      // Disable mixed-content upgrading as this test is expecting an HTTP load
    367      await SpecialPowers.pushPrefEnv({
    368          set: [["security.mixed_content.upgrade_display_content", false]]
    369      });
    370 
    371      // Test opening details objects
    372      await openNode("detailsOpen", "summaryOpen", true);
    373      await openNode("detailsOpen", "summaryOpen", false);
    374      await openNode("detailsOpen1", "summaryOpen1", true);
    375      await openNode("detailsOpen2", "summaryOpen2", true);
    376      await openNode("detailsOpen3", "summaryOpen3", true);
    377      await openNode("detailsOpen4", "summaryOpen4", true);
    378      await openNode("detailsOpen5", "summaryOpen5", true);
    379      await openNode("detailsOpen6", "summaryOpen6", true);
    380 
    381      // Test delayed editable state change
    382      var doc = document.getElementById("iframe").contentDocument;
    383      await makeEditableDoc(doc);
    384 
    385      // invalid state change
    386      await invalidInput("email");
    387 
    388      // checked state change
    389      await changeCheckInput("checkbox", true);
    390      await changeCheckInput("checkbox", false);
    391      await changeCheckInput("radio", true);
    392      await changeCheckInput("radio", false);
    393 
    394      // required state change
    395      await changeRequiredState("checkbox", true);
    396 
    397      // file input inherited state changes
    398      await stateChangeOnFileInput("file", "aria-busy", "true",
    399                                   STATE_BUSY, false, true);
    400      await stateChangeOnFileInput("file", "aria-required", "true",
    401                                   STATE_REQUIRED, false, true);
    402      await stateChangeOnFileInput("file", "aria-invalid", "true",
    403                                   STATE_INVALID, false, true);
    404 
    405      await dupeStateChange("div", "aria-busy", "true",
    406                            STATE_BUSY, false, true);
    407      await oppositeStateChange("div", "aria-busy",
    408                                STATE_BUSY, false);
    409 
    410      await echoingStateChange("text1", "aria-disabled", "disabled", "true",
    411                               EXT_STATE_ENABLED, true, false);
    412      await echoingStateChange("text1", "aria-disabled", "disabled", null,
    413                               EXT_STATE_ENABLED, true, true);
    414 
    415      await testReadOnly();
    416 
    417      await testReadonlyUntilEditable();
    418 
    419      await testHasPopup();
    420 
    421      await toggleStateChange("textbox", "aria-multiline", EXT_STATE_MULTI_LINE, true);
    422 
    423      await testDefaultSubmitChange();
    424 
    425      await testAnimatedImage();
    426 
    427      await testImageLoad();
    428 
    429      await testMultiSelectable("listbox", "aria-multiselectable");
    430 
    431      await testMultiSelectable("select", "multiple");
    432 
    433      await testAutocomplete();
    434 
    435      SimpleTest.finish();
    436    }
    437 
    438    SimpleTest.waitForExplicitFinish();
    439    addA11yLoadEvent(doTests);
    440  </script>
    441 </head>
    442 <style>
    443  details.openBefore::before{
    444    content: "before detail content: ";
    445    background: blue;
    446  }
    447  summary.openBefore::before{
    448    content: "before summary content: ";
    449    background: green;
    450  }
    451  details.openAfter::after{
    452    content: " :after detail content";
    453    background: blue;
    454  }
    455  summary.openAfter::after{
    456    content: " :after summary content";
    457    background: green;
    458  }
    459 </style>
    460 <body>
    461 
    462  <a target="_blank"
    463     href="https://bugzilla.mozilla.org/show_bug.cgi?id=564471"
    464     title="Make state change events async">
    465    Bug 564471
    466  </a>
    467  <a target="_blank"
    468     href="https://bugzilla.mozilla.org/show_bug.cgi?id=555728"
    469     title="Fire a11y event based on HTML5 constraint validation">
    470    Bug 555728
    471  </a>
    472  <a target="_blank"
    473     href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017"
    474     title="File input control should be propogate states to descendants">
    475    Bug 699017
    476  </a>
    477  <a target="_blank"
    478     href="https://bugzilla.mozilla.org/show_bug.cgi?id=788389"
    479     title="Fire statechange event whenever checked state is changed not depending on focused state">
    480    Bug 788389
    481  </a>
    482  <a target="_blank"
    483     href="https://bugzilla.mozilla.org/show_bug.cgi?id=926812"
    484     title="State change event not fired when both disabled and aria-disabled are toggled">
    485    Bug 926812
    486  </a>
    487 
    488  <p id="display"></p>
    489  <div id="content" style="display: none"></div>
    490  <pre id="test">
    491  </pre>
    492 
    493  <!-- open -->
    494  <details id="detailsOpen"><summary id="summaryOpen">open</summary>details can be opened</details>
    495  <details id="detailsOpen1">order doesn't matter<summary id="summaryOpen1">open</summary></details>
    496  <details id="detailsOpen2"><div>additional elements don't matter</div><summary id="summaryOpen2">open</summary></details>
    497  <details id="detailsOpen3" class="openBefore"><summary id="summaryOpen3">summary</summary>content</details>
    498  <details id="detailsOpen4" class="openAfter"><summary id="summaryOpen4">summary</summary>content</details>
    499  <details id="detailsOpen5"><summary id="summaryOpen5" class="openBefore">summary</summary>content</details>
    500  <details id="detailsOpen6"><summary id="summaryOpen6" class="openAfter">summary</summary>content</details>
    501 
    502 
    503  <div id="testContainer">
    504    <iframe id="iframe"></iframe>
    505  </div>
    506 
    507  <input id="email" type='email'>
    508 
    509  <input id="checkbox" type="checkbox">
    510  <input id="radio" type="radio">
    511 
    512  <input id="file" type="file">
    513 
    514  <div id="div"></div>
    515 
    516  <!-- A sentinal guards from events of interest being fired after it emits a state change -->
    517  <div id="sentinel"></div>
    518 
    519  <input id="text1">
    520 
    521  <div id="textbox" role="textbox" aria-multiline="false">hello</div>
    522 
    523  <form id="form">
    524    <button id="default-button">hello</button>
    525    <button>world</button>
    526    <input id="input">
    527    <input id="input-autocomplete-off" autocomplete="off">
    528    <input id="input-list" list="browsers">
    529    <input id="input-password" type="password">
    530    <datalist id="browsers">
    531      <option value="Internet Explorer">
    532      <option value="Firefox">
    533      <option value="Google Chrome">
    534      <option value="Opera">
    535      <option value="Safari">
    536    </datalist>
    537  </form>
    538 
    539  <div id="article" role="article">hello</div>
    540 
    541  <img id="animated-image" src="../animated-gif.gif">
    542 
    543  <ul id="listbox" role="listbox">
    544    <li role="option">one</li>
    545    <li role="option">two</li>
    546    <li role="option">three</li>
    547    <li role="option">four</li>
    548    <li role="option">five</li>
    549  </ul>
    550 
    551  <select id="select" size="2">
    552    <option>one</option>
    553    <option>two</option>
    554    <option>three</option>
    555    <option>four</option>
    556    <option>five</option>
    557    <option>size</option>
    558  </select>
    559 
    560  <div id="eventdump"></div>
    561 
    562  <div id="eventdump"></div>
    563  <button id="popupButton">action</button>
    564 </body>
    565 </html>