tor-browser

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

popover-utils.js (8492B)


      1 function waitForRender() {
      2  return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
      3 }
      4 
      5 async function clickOn(element, useTouch) {
      6  await waitForRender();
      7  let rect = element.getBoundingClientRect();
      8  let actions = new test_driver.Actions();
      9  // FIXME: Switch to pointerMove(0, 0, {origin: element}) once
     10  // https://github.com/web-platform-tests/wpt/issues/41257 is fixed.
     11  if (useTouch) {
     12    actions.addPointer('touch1', 'touch');
     13  }
     14  await actions
     15      .pointerMove(Math.round(rect.x + rect.width / 2), Math.round(rect.y + rect.height / 2), {})
     16      .pointerDown({button: actions.ButtonType.LEFT})
     17      .pointerUp({button: actions.ButtonType.LEFT})
     18      .send();
     19  await waitForRender();
     20 }
     21 async function sendTab() {
     22  await waitForRender();
     23  const kTab = '\uE004';
     24  await test_driver.send_keys(document.activeElement || document.documentElement, kTab);
     25  await waitForRender();
     26 }
     27 async function sendShiftTab() {
     28  await waitForRender();
     29  const kShift = '\uE008';
     30  const kTab = '\uE004';
     31  await new test_driver.Actions()
     32    .keyDown(kShift)
     33    .keyDown(kTab)
     34    .keyUp(kTab)
     35    .keyUp(kShift)
     36    .send();
     37  await waitForRender();
     38 }
     39 async function sendEscape() {
     40  await waitForRender();
     41  await test_driver.send_keys(document.activeElement || document.documentElement,'\uE00C'); // Escape
     42  await waitForRender();
     43 }
     44 async function sendEnter() {
     45  await waitForRender();
     46  await test_driver.send_keys(document.activeElement || document.documentElement,'\uE007'); // Enter
     47  await waitForRender();
     48 }
     49 function isElementVisible(el) {
     50  return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
     51 }
     52 async function finishAnimations(popover) {
     53  popover.getAnimations({subtree: true}).forEach(animation => animation.finish());
     54  await waitForRender();
     55 }
     56 
     57 // This is a "polyfill" of sorts for the `defaultopen` attribute.
     58 // It can be called before window.load is complete, and it will
     59 // show defaultopen popovers according to the rules previously part
     60 // of the popover API: any popover=manual popover can be shown this
     61 // way, and only the first popover=auto popover.
     62 function showDefaultopenPopoversOnLoad() {
     63  function show() {
     64    const popovers = Array.from(document.querySelectorAll('[popover][defaultopen]'));
     65    popovers.forEach((p) => {
     66        // The showPopover calls below aren't guarded by a check on the popover
     67        // open/closed status. If they throw exceptions, this function was
     68        // probably called at a bad time. However, a check is made for open
     69        // <dialog open> elements.
     70        if (p instanceof HTMLDialogElement && p.hasAttribute('open'))
     71          return;
     72        switch (p.popover) {
     73          case 'auto':
     74            if (!document.querySelector('[popover]:popover-open'))
     75              p.showPopover();
     76            return;
     77          case 'manual':
     78            p.showPopover();
     79            return;
     80          default:
     81            assert_unreached(`Unknown popover type ${p.popover}`);
     82        }
     83      });
     84  }
     85  if (document.readyState === 'complete') {
     86    show();
     87  } else {
     88    window.addEventListener('load',show,{once:true});
     89  }
     90 }
     91 
     92 function assertPopoverVisibility(popover, isPopover, expectedVisibility, message) {
     93  const isVisible = isElementVisible(popover);
     94  assert_equals(isVisible, expectedVisibility,`${message}: Expected this element to be ${expectedVisibility ? "visible" : "not visible"}`);
     95  // Check other things related to being visible or not:
     96  if (isVisible) {
     97    assert_not_equals(window.getComputedStyle(popover).display,'none');
     98    assert_equals(popover.matches(':popover-open'),isPopover,`${message}: Visible popovers should match :popover-open`);
     99  } else {
    100    assert_equals(window.getComputedStyle(popover).display,'none',`${message}: Non-showing popovers should have display:none`);
    101    assert_false(popover.matches(':popover-open'),`${message}: Non-showing popovers should *not* match :popover-open`);
    102  }
    103 }
    104 
    105 function assertIsFunctionalPopover(popover, checkVisibility) {
    106  assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'A popover should start out hidden');
    107  popover.showPopover();
    108  if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After showPopover(), a popover should be visible');
    109  popover.showPopover(); // Calling showPopover on a showing popover should not throw.
    110  popover.hidePopover();
    111  if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After hidePopover(), a popover should be hidden');
    112  popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw.
    113  popover.togglePopover();
    114  if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After togglePopover() on hidden popover, it should be visible');
    115  popover.togglePopover();
    116  if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After togglePopover() on visible popover, it should be hidden');
    117  popover.togglePopover(/*force=*/true);
    118  if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After togglePopover(true) on hidden popover, it should be visible');
    119  popover.togglePopover(/*force=*/true);
    120  if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After togglePopover(true) on visible popover, it should be visible');
    121  popover.togglePopover(/*force=*/false);
    122  if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After togglePopover(false) on visible popover, it should be hidden');
    123  popover.togglePopover(/*force=*/false);
    124  if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After togglePopover(false) on hidden popover, it should be hidden');
    125  const parent = popover.parentElement;
    126  popover.remove();
    127  assert_throws_dom("InvalidStateError",() => popover.showPopover(),'Calling showPopover on a disconnected popover should throw InvalidStateError');
    128  popover.hidePopover(); // Calling hidePopover on a disconnected popover should not throw.
    129  assert_throws_dom("InvalidStateError",() => popover.togglePopover(),'Calling hidePopover on a disconnected popover should throw InvalidStateError');
    130  parent.appendChild(popover);
    131 }
    132 
    133 function assertNotAPopover(nonPopover) {
    134  // If the non-popover element nonetheless has a 'popover' attribute, it should
    135  // be invisible. Otherwise, it should be visible.
    136  const expectVisible = !nonPopover.hasAttribute('popover');
    137  assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'A non-popover should start out visible');
    138  assert_throws_dom("NotSupportedError",() => nonPopover.showPopover(),'Calling showPopover on a non-popover should throw NotSupported');
    139  assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'Calling showPopover on a non-popover should leave it visible');
    140  assert_throws_dom("NotSupportedError",() => nonPopover.hidePopover(),'Calling hidePopover on a non-popover should throw NotSupported');
    141  assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'Calling hidePopover on a non-popover should leave it visible');
    142  assert_throws_dom("NotSupportedError",() => nonPopover.togglePopover(),'Calling togglePopover on a non-popover should throw NotSupported');
    143  assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'Calling togglePopover on a non-popover should leave it visible');
    144 }
    145 
    146 async function verifyFocusOrder(order,description) {
    147  order[0].focus();
    148  for(let i=0;i<order.length;++i) {
    149    // Press tab between each check, excluding first (because it should already be focused)
    150    // and the last (because tabbing after the last element may send focus into browser chrome).
    151    if (i != 0) {
    152      await sendTab();
    153    }
    154    const control = order[i];
    155    assert_equals(document.activeElement,control,`${description}: Step ${i+1}`);
    156  }
    157  for(let i=order.length-1;i>=0;--i) {
    158    const control = order[i];
    159    assert_equals(document.activeElement,control,`${description}: Step ${i+1} (backwards)`);
    160    // Press shift+tab between each check, excluding last (because it should already be focused)
    161    // and the first (because shift+tabbing after the last element may send focus into browser chrome).
    162    if (i != 0) {
    163      await sendShiftTab();
    164    }
    165  }
    166 }