tor-browser

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

popover-events.html (8916B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8" />
      3 <title>Popover events</title>
      4 <link rel="author" href="mailto:masonf@chromium.org">
      5 <link rel=help href="https://open-ui.org/components/popover.research.explainer">
      6 <link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
      7 <script src="/resources/testharness.js"></script>
      8 <script src="/resources/testharnessreport.js"></script>
      9 <script src="resources/popover-utils.js"></script>
     10 
     11 <div popover>Popover</div>
     12 
     13 <script>
     14 function getPopoverAndSignal(t) {
     15  const popover = document.querySelector('[popover]');
     16  const controller = new AbortController();
     17  const signal = controller.signal;
     18  t.add_cleanup(() => controller.abort());
     19  return {popover, signal};
     20 }
     21 window.onload = () => {
     22  for(const method of ["listener","attribute"]) {
     23    promise_test(async t => {
     24      const {popover,signal} = getPopoverAndSignal(t);
     25      assert_false(popover.matches(':popover-open'));
     26      let showCount = 0;
     27      let afterShowCount = 0;
     28      let hideCount = 0;
     29      let afterHideCount = 0;
     30      function listener(e) {
     31        assert_false(e.bubbles,'toggle events should not bubble');
     32        if (e.type === "beforetoggle") {
     33          if (e.newState === "open") {
     34            ++showCount;
     35            assert_equals(e.oldState,"closed",'The "beforetoggle" event should be fired before the popover is open');
     36            assert_false(e.target.matches(':popover-open'),'The popover should *not* be in the :popover-open state when the opening event fires.');
     37            assert_true(e.cancelable,'beforetoggle should be cancelable only for the "show" transition');
     38          } else {
     39            ++hideCount;
     40            assert_equals(e.newState,"closed",'Popover toggleevent states should be "open" and "closed"');
     41            assert_equals(e.oldState,"open",'The "beforetoggle" event should be fired before the popover is closed')
     42            assert_true(e.target.matches(':popover-open'),'The popover should be in the :popover-open state when the hiding event fires.');
     43            assert_false(e.cancelable,'beforetoggle should be cancelable only for the "show" transition');
     44            e.preventDefault(); // beforetoggle should be cancelable only for the "show" transition
     45          }
     46        } else {
     47          assert_equals(e.type,"toggle",'Popover events should be "beforetoggle" and "toggle"')
     48          assert_false(e.cancelable,'toggle should never be cancelable');
     49          e.preventDefault(); // toggle should never be cancelable
     50          if (e.newState === "open") {
     51            ++afterShowCount;
     52            if (document.body.contains(e.target)) {
     53              assert_true(e.target.matches(':popover-open'),'The popover should be in the :popover-open state when the after opening event fires.');
     54            }
     55          } else {
     56            ++afterHideCount;
     57            assert_equals(e.newState,"closed",'Popover toggleevent states should be "open" and "closed"');
     58            assert_false(e.target.matches(':popover-open'),'The popover should *not* be in the :popover-open state when the after hiding event fires.');
     59          }
     60          e.preventDefault(); // "toggle" should not be cancelable.
     61        }
     62      };
     63      switch (method) {
     64        case "listener":
     65          // These events do *not* bubble.
     66          popover.addEventListener('beforetoggle', listener, {signal});
     67          popover.addEventListener('toggle', listener, {signal});
     68          break;
     69        case "attribute":
     70          assert_false(popover.hasAttribute('onbeforetoggle'));
     71          t.add_cleanup(() => popover.removeAttribute('onbeforetoggle'));
     72          popover.onbeforetoggle = listener;
     73          assert_false(popover.hasAttribute('ontoggle'));
     74          t.add_cleanup(() => popover.removeAttribute('ontoggle'));
     75          popover.ontoggle = listener;
     76          break;
     77        default: assert_unreached();
     78      }
     79      assert_equals(0,showCount);
     80      assert_equals(0,hideCount);
     81      assert_equals(0,afterShowCount);
     82      assert_equals(0,afterHideCount);
     83      popover.showPopover();
     84      assert_true(popover.matches(':popover-open'));
     85      assert_equals(1,showCount);
     86      assert_equals(0,hideCount);
     87      assert_equals(0,afterShowCount);
     88      assert_equals(0,afterHideCount);
     89      await waitForRender();
     90      assert_equals(1,afterShowCount,'toggle show is fired asynchronously');
     91      assert_equals(0,afterHideCount);
     92      assert_true(popover.matches(':popover-open'));
     93      popover.hidePopover();
     94      assert_false(popover.matches(':popover-open'));
     95      assert_equals(1,showCount);
     96      assert_equals(1,hideCount);
     97      assert_equals(1,afterShowCount);
     98      assert_equals(0,afterHideCount);
     99      await waitForRender();
    100      assert_equals(1,afterShowCount);
    101      assert_equals(1,afterHideCount,'toggle hide is fired asynchronously');
    102      // No additional events
    103      await waitForRender();
    104      await waitForRender();
    105      assert_false(popover.matches(':popover-open'));
    106      assert_equals(1,showCount);
    107      assert_equals(1,hideCount);
    108      assert_equals(1,afterShowCount);
    109      assert_equals(1,afterHideCount);
    110    }, `The "beforetoggle" event (${method}) get properly dispatched for popovers`);
    111  }
    112 
    113  promise_test(async t => {
    114    const {popover,signal} = getPopoverAndSignal(t);
    115    let cancel = true;
    116    popover.addEventListener('beforetoggle',(e) => {
    117      if (e.newState !== "open")
    118        return;
    119      if (cancel)
    120        e.preventDefault();
    121    }, {signal});
    122    assert_false(popover.matches(':popover-open'));
    123    popover.showPopover();
    124    assert_false(popover.matches(':popover-open'),'The "beforetoggle" event should be cancelable for the "opening" transition');
    125    cancel = false;
    126    popover.showPopover();
    127    assert_true(popover.matches(':popover-open'));
    128    popover.hidePopover();
    129    assert_false(popover.matches(':popover-open'));
    130  }, 'The "beforetoggle" event is cancelable for the "opening" transition');
    131 
    132  promise_test(async t => {
    133    const {popover,signal} = getPopoverAndSignal(t);
    134    popover.addEventListener('beforetoggle',(e) => {
    135      assert_not_equals(e.newState,"closed",'The "beforetoggle" event was fired for the closing transition');
    136    }, {signal});
    137    assert_false(popover.matches(':popover-open'));
    138    popover.showPopover();
    139    assert_true(popover.matches(':popover-open'));
    140    t.add_cleanup(() => {document.body.appendChild(popover);});
    141    popover.remove();
    142    await waitForRender(); // Check for async events also
    143    await waitForRender(); // Check for async events also
    144    assert_false(popover.matches(':popover-open'));
    145  }, 'The "beforetoggle" event is not fired for element removal');
    146 
    147  promise_test(async t => {
    148    const {popover,signal} = getPopoverAndSignal(t);
    149    let events;
    150    function resetEvents() {
    151      events = {
    152        singleShow: false,
    153        singleHide: false,
    154        coalescedShow: false,
    155        coalescedHide: false,
    156      };
    157    }
    158    function setEvent(type) {
    159      assert_equals(events[type],false,'event repeated');
    160      events[type] = true;
    161    }
    162    function assertOnly(type,msg) {
    163      Object.keys(events).forEach(val => {
    164        assert_equals(events[val],val===type,`${msg} (${val})`);
    165      });
    166    }
    167    popover.addEventListener('toggle',(e) => {
    168      switch (e.newState) {
    169        case "open":
    170          switch (e.oldState) {
    171            case "open": setEvent('coalescedShow'); break;
    172            case "closed": setEvent('singleShow'); break;
    173            default: assert_unreached();
    174          }
    175          break;
    176        case "closed":
    177          switch (e.oldState) {
    178            case "closed": setEvent('coalescedHide'); break;
    179            case "open": setEvent('singleHide'); break;
    180            default: assert_unreached();
    181          }
    182          break;
    183        default: assert_unreached();
    184      }
    185    }, {signal});
    186 
    187    resetEvents();
    188    assertOnly('none');
    189    assert_false(popover.matches(':popover-open'));
    190    popover.showPopover();
    191    await waitForRender();
    192    assert_true(popover.matches(':popover-open'));
    193    assertOnly('singleShow','Single event should have been fired, which is a "show"');
    194 
    195    resetEvents();
    196    popover.hidePopover();
    197    popover.showPopover(); // Immediate re-show
    198    await waitForRender();
    199    assert_true(popover.matches(':popover-open'));
    200    assertOnly('coalescedShow','Single coalesced event should have been fired, which is a "show"');
    201 
    202    resetEvents();
    203    popover.hidePopover();
    204    await waitForRender();
    205    assertOnly('singleHide','Single event should have been fired, which is a "hide"');
    206    assert_false(popover.matches(':popover-open'));
    207 
    208    resetEvents();
    209    popover.showPopover();
    210    popover.hidePopover(); // Immediate re-hide
    211    await waitForRender();
    212    assertOnly('coalescedHide','Single coalesced event should have been fired, which is a "hide"');
    213    assert_false(popover.matches(':popover-open'));
    214  }, 'The "toggle" event is coalesced');
    215 };
    216 </script>