tor-browser

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

toggle-events.html (11406B)


      1 <!doctype html>
      2 <link rel="author" href="mailto:jarhar@chromium.org" />
      3 <link rel="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" />
      4 <link rel="help" href="https://github.com/whatwg/html/pull/10091" />
      5 <link rel="help" href="https://github.com/whatwg/html/issues/9733" />
      6 <script src="/resources/testharness.js"></script>
      7 <script src="/resources/testharnessreport.js"></script>
      8 
      9 <dialog id="mydialog">dialog</dialog>
     10 
     11 <script>
     12  ["show", "showModal"].forEach((methodName) => {
     13    const waitForTick = () =>  new Promise(resolve => step_timeout(resolve, 0));
     14 
     15    promise_test(async () => {
     16      let openingBeforetoggle = null;
     17      let openingToggle = null;
     18 
     19      mydialog.addEventListener(
     20        "beforetoggle",
     21        (event) => {
     22          assert_equals(
     23            event.oldState,
     24            "closed",
     25            'Opening beforetoggle should have oldState be "closed".',
     26          );
     27          assert_equals(
     28            event.newState,
     29            "open",
     30            'Opening beforetoggle should have newState be "open".',
     31          );
     32          assert_false(
     33            mydialog.hasAttribute("open"),
     34            "Opening beforetoggle should fire before open attribute is added.",
     35          );
     36          openingBeforetoggle = event;
     37        },
     38        { once: true },
     39      );
     40 
     41      mydialog.addEventListener(
     42        "toggle",
     43        (event) => {
     44          assert_equals(
     45            event.oldState,
     46            "closed",
     47            'Opening toggle should have oldState be "closed".',
     48          );
     49          assert_equals(
     50            event.newState,
     51            "open",
     52            'Opening toggle should have newState be "open".',
     53          );
     54          assert_true(
     55            mydialog.hasAttribute("open"),
     56            "Opening toggle should fire after open attribute is added.",
     57          );
     58          openingToggle = event;
     59        },
     60        { once: true },
     61      );
     62 
     63      mydialog[methodName]();
     64      assert_true(
     65        !!openingBeforetoggle,
     66        "Opening beforetoggle should fire synchronously.",
     67      );
     68      assert_false(
     69        !!openingToggle,
     70        "Opening toggle should fire asynchronously.",
     71      );
     72 
     73      await waitForTick();
     74      assert_true(
     75        !!openingToggle,
     76        "Opening toggle should have fired after tick.",
     77      );
     78 
     79      let closingBeforetoggle = null;
     80      let closingToggle = null;
     81 
     82      mydialog.addEventListener(
     83        "beforetoggle",
     84        (event) => {
     85          assert_equals(
     86            event.oldState,
     87            "open",
     88            'Closing beforetoggle should have oldState be "open".',
     89          );
     90          assert_equals(
     91            event.newState,
     92            "closed",
     93            'Closing beforetoggle should have newState be "closed".',
     94          );
     95          assert_true(
     96            mydialog.hasAttribute("open"),
     97            "Closing beforetoggle should fire before open attribute is removed.",
     98          );
     99          closingBeforetoggle = event;
    100        },
    101        { once: true },
    102      );
    103      mydialog.addEventListener(
    104        "toggle",
    105        (event) => {
    106          assert_equals(
    107            event.oldState,
    108            "open",
    109            'Closing toggle should have oldState be "open".',
    110          );
    111          assert_equals(
    112            event.newState,
    113            "closed",
    114            'Closing toggle should have newState be "closed".',
    115          );
    116          assert_false(
    117            mydialog.hasAttribute("open"),
    118            "Closing toggle should fire after open attribute is removed.",
    119          );
    120          closingToggle = event;
    121        },
    122        { once: true },
    123      );
    124 
    125      mydialog.close();
    126      assert_true(
    127        !!closingBeforetoggle,
    128        "Closing beforetoggle should fire synchronously.",
    129      );
    130      assert_false(
    131        !!closingToggle,
    132        "Closing toggle should fire asynchronously.",
    133      );
    134 
    135      await waitForTick();
    136      assert_true(
    137        !!closingToggle,
    138        "Closing toggle should have fired after tick.",
    139      );
    140    }, `dialog.${methodName}() should fire beforetoggle and toggle events.`);
    141 
    142    promise_test(async () => {
    143      let openingBeforetoggle = null;
    144      let openingToggle = null;
    145 
    146      mydialog.addEventListener(
    147        "beforetoggle",
    148        (event) => {
    149          event.preventDefault();
    150          openingBeforetoggle = event;
    151        },
    152        { once: true },
    153      );
    154 
    155      mydialog.addEventListener(
    156        "toggle",
    157        (event) => {
    158          openingToggle = event;
    159        },
    160        { once: true },
    161      );
    162 
    163      mydialog[methodName]();
    164      assert_true(
    165        !!openingBeforetoggle,
    166        "Opening beforetoggle should fire synchronously.",
    167      );
    168      assert_false(
    169        !!openingToggle,
    170        "Opening toggle should fire.",
    171      );
    172 
    173      await waitForTick();
    174      assert_false(
    175        !!openingToggle,
    176        "Opening toggle should still not have fired.",
    177      );
    178 
    179      assert_false(mydialog.open, 'dialog should not be open');
    180    }, `dialog.${methodName}() should fire cancelable beforetoggle which does not open dialog if canceled`);
    181 
    182    promise_test(async () => {
    183      let openCloseToggleEvent = null;
    184      mydialog.addEventListener(
    185        "toggle",
    186        (event) => {
    187          assert_equals(
    188            event.oldState,
    189            "closed",
    190            'Opening and closing dialog should result in oldState being "closed".',
    191          );
    192          assert_equals(
    193            event.newState,
    194            "closed",
    195            'Opening and closing dialog should result in newState being "closed".',
    196          );
    197          assert_false(
    198            mydialog.hasAttribute("open"),
    199            "Opening and closing dialog should result in open attribute being removed.",
    200          );
    201          openCloseToggleEvent = event;
    202        },
    203        { once: true },
    204      );
    205 
    206      mydialog[methodName]();
    207      assert_false(
    208        !!openCloseToggleEvent,
    209        "Toggle event should not fire synchronously.",
    210      );
    211      mydialog.close();
    212      await waitForTick();
    213      assert_true(
    214        !!openCloseToggleEvent,
    215        "Toggle event should have fired after tick.",
    216      );
    217 
    218      mydialog[methodName]();
    219      await waitForTick();
    220 
    221      let closeOpenToggleEvent = null;
    222      mydialog.addEventListener(
    223        "toggle",
    224        (event) => {
    225          assert_equals(
    226            event.oldState,
    227            "open",
    228            'Closing and opening dialog should result in oldState being "open".',
    229          );
    230          assert_equals(
    231            event.newState,
    232            "open",
    233            'Closing and opening dialog should result in newState being "open".',
    234          );
    235          assert_true(
    236            mydialog.hasAttribute("open"),
    237            "Closing and opening dialog should result in open attribute being added.",
    238          );
    239          closeOpenToggleEvent = event;
    240        },
    241        { once: true },
    242      );
    243 
    244      mydialog.close();
    245      assert_false(
    246        !!closeOpenToggleEvent,
    247        "Toggle event should not fire synchronously.",
    248      );
    249      mydialog[methodName]();
    250      await waitForTick();
    251      assert_true(
    252        !!closeOpenToggleEvent,
    253        "Toggle event should have fired after tick.",
    254      );
    255 
    256      // Clean up for the next test.
    257      mydialog.close();
    258      await waitForTick();
    259    }, `dialog.${methodName}() should coalesce asynchronous toggle events.`);
    260 
    261    promise_test(async (t) => {
    262      let attributeChanges = 0;
    263      const mo = new MutationObserver((records) => {
    264        attributeChanges += records.length;
    265      });
    266      mo.observe(mydialog, { attributeFilter: ['open'] });
    267      t.add_cleanup(() => {
    268        mo.disconnect();
    269      });
    270      mydialog.addEventListener("beforetoggle", () => {
    271        mydialog[methodName]();
    272      }, { once: true });
    273 
    274      mydialog[methodName]();
    275      assert_true(mydialog.open, "Dialog is open");
    276      await waitForTick();
    277      mo.takeRecords();
    278      assert_equals(attributeChanges, 1, "Should have set open once");
    279 
    280      attributeChanges = 0;
    281      mydialog.addEventListener("beforetoggle", () => {
    282        mydialog.close();
    283      }, { once: true });
    284 
    285      mydialog.close();
    286      assert_false(mydialog.open, "Dialog is closed");
    287      await waitForTick();
    288      mo.takeRecords();
    289      assert_equals(attributeChanges, 1, "Should have removed open once");
    290    }, `dialog.${methodName}() should not double-set open/close if beforetoggle re-opens`);
    291 
    292    promise_test(async (t) => {
    293      const abortController = new AbortController();
    294      const signal = abortController.signal;
    295      const mydialog = document.getElementById("mydialog");
    296      t.add_cleanup(() => {
    297        abortController.abort();
    298        mydialog.close();
    299        document.body.prepend(mydialog);
    300      });
    301      mydialog.addEventListener("beforetoggle", () => {
    302        mydialog.remove();
    303      }, { once: true });
    304      let toggleEventCounter = 0;
    305      mydialog.addEventListener(
    306        "toggle",
    307        (event) => {
    308          toggleEventCounter += 1;
    309        },
    310        { signal }
    311      );
    312 
    313      mydialog[methodName]();
    314      assert_false(mydialog.isConnected, "Dialog is not connected");
    315      if (methodName == 'show') {
    316        assert_true(mydialog.open, "Dialog did open");
    317      } else {
    318        assert_false(mydialog.open, "Dialog did not open");
    319        assert_false(mydialog.matches(':modal'), "Dialog is not modal");
    320      }
    321      await waitForTick();
    322      if (methodName == 'show') {
    323        assert_equals(toggleEventCounter, 1, "toggle event was fired");
    324      } else {
    325        assert_equals(toggleEventCounter, 0, "toggle event not fired");
    326      }
    327      // Clean up for the next test.
    328      document.body.prepend(mydialog);
    329      mydialog.close();
    330      await waitForTick();
    331    }, `dialog.${methodName}() should not open if beforetoggle removes`);
    332 
    333    promise_test(async (t) => {
    334      assert_true(document.body.contains(mydialog),'still in the document');
    335      assert_false(mydialog.open,'initially closed');
    336      const abortController = new AbortController();
    337      const signal = abortController.signal;
    338      t.add_cleanup(() => {
    339        try { mydialog.hidePopover(); } catch {}
    340        try { mydialog.close(); } catch {}
    341        mydialog.removeAttribute('popover');
    342        abortController.abort();
    343        waitForTick(); // Note that cleanups can't await
    344      });
    345      mydialog.setAttribute('popover', '');
    346      mydialog.addEventListener("beforetoggle", () => {
    347        mydialog.showPopover();
    348      }, { once: true });
    349      let toggleEventCounter = 0;
    350      mydialog.addEventListener(
    351        "toggle",
    352        (event) => {
    353          toggleEventCounter += 1;
    354        },
    355        { signal }
    356      );
    357 
    358      mydialog[methodName]();
    359      if (methodName == 'show') {
    360        assert_true(mydialog.open, "Dialog did open");
    361      } else {
    362        assert_false(mydialog.open, "Dialog did not open");
    363        assert_false(mydialog.matches(':modal'), "Dialog is not modal");
    364      }
    365      await waitForTick();
    366      if (methodName == 'show') {
    367        assert_equals(toggleEventCounter, 2, "toggle event was fired for show+showPopover");
    368      } else {
    369        assert_equals(toggleEventCounter, 1, "toggle event was fired for showPopover");
    370      }
    371      // Clean up for the next test.
    372      mydialog.close();
    373      await waitForTick();
    374    }, `dialog.${methodName}() should not open if beforetoggle calls showPopover`);
    375  });
    376 </script>