tor-browser

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

interestfor-delay-start.tentative.html (10592B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8" />
      3 <link rel="author" href="mailto:masonf@chromium.org">
      4 <link rel="help" href="https://open-ui.org/components/interest-invokers.explainer">
      5 <meta name="timeout" content="long">
      6 <script src="/resources/testharness.js"></script>
      7 <script src="/resources/testharnessreport.js"></script>
      8 <script src="/resources/testdriver.js"></script>
      9 <script src="/resources/testdriver-actions.js"></script>
     10 <script src="/resources/testdriver-vendor.js"></script>
     11 <script src="resources/invoker-utils.js"></script>
     12 
     13 <meta name=variant content=?layout=plain&method=hover>
     14 <meta name=variant content=?layout=nested&method=hover>
     15 <meta name=variant content=?layout=nested-offset&method=hover>
     16 <meta name=variant content=?layout=plain&method=focus>
     17 <meta name=variant content=?layout=nested&method=focus>
     18 <meta name=variant content=?layout=nested-offset&method=focus>
     19 
     20 <body>
     21 <script>
     22 // The invokerLayout and invokerMethod parameters are provided by variants, to
     23 // effectively split this (slow) test into multiple runs.
     24 const urlParams = new URLSearchParams(window.location.search);
     25 invokerLayout = urlParams.get('layout');
     26 invokerMethod = urlParams.get('method');
     27 description = `layout ${invokerLayout}, method ${invokerMethod}`;
     28 
     29 // NOTE about testing methodology:
     30 // This test uses popovers as an invoker target, and checks whether they are
     31 // triggered *after* the appropriate hover/focus delay. The delay used for
     32 // testing is kept low, to avoid this test taking too long, but that means that
     33 // sometimes on a slow bot/client, the hover delay can elapse before we are able
     34 // to check the popover status. And that can make this test flaky. To avoid
     35 // that, the msSinceMouseOver() function is used to check that not-too-much time
     36 // has passed, and if it has, the test is simply skipped. Because of this
     37 // methodology, many/most of these tests will pass on browsers that do not
     38 // implement `interestfor`. See the `interestfor-basic-delays` test.
     39 
     40 const invokerShowDelayMs = 100; // The CSS delay setting.
     41 const hoverFocusWaitTimeMs = 200; // How long to wait to cover the delay for sure.
     42 
     43 async function makePopoverAndInvoker(test, invokerLayout, showdelayMs) {
     44  // This ensures the tests in this file don't succeed sometimes, due to the above NOTE.
     45  assert_true(HTMLAnchorElement.prototype.hasOwnProperty('interestForElement'),'interestfor is not supported');
     46  if (showdelayMs === undefined) {
     47    showdelayMs = invokerShowDelayMs;
     48  }
     49  let {popover, invoker, unrelated} = await createPopoverAndInvokerForHoverTests(test, showdelayMs, /*hidedelayMs*/10000000);
     50  invoker.setAttribute('class','invoker');
     51  const originalInvoker = invoker;
     52  const reassignInvokerTargetFn = (p) => {originalInvoker.interestForElement = p};
     53  switch (invokerLayout) {
     54    case 'plain':
     55      // Invoker is just a button.
     56      invoker.textContent = 'Invoker';
     57      break;
     58    case 'nested':
     59      // Invoker is just a button containing a div.
     60      const child1 = invoker.appendChild(document.createElement('div'));
     61      child1.textContent = 'Invoker';
     62      break;
     63    case 'nested-offset':
     64      // Invoker is a child of the invoking button, and is not contained within
     65      // the bounds of the interestfor element.
     66      invoker.textContent = 'Invoker';
     67      // Reassign invoker to the child:
     68      invoker = invoker.appendChild(document.createElement('div'));
     69      invoker.textContent = 'Invoker child';
     70      invoker.tabIndex = 0;
     71      invoker.setAttribute('style','position:relative; top:300px; left:300px;');
     72      break;
     73    default:
     74      assert_unreached(`Invalid invokerLayout ${invokerLayout}`);
     75  }
     76  test.add_cleanup(() => {
     77    originalInvoker.remove();
     78  });
     79  assert_false(popover.matches(':popover-open'),'Popover should start out closed');
     80  return {popover,invoker,unrelated,reassignInvokerTargetFn};
     81 }
     82 
     83 promise_test(async (t) => {
     84  const {popover,invoker} = await makePopoverAndInvoker(t,invokerLayout);
     85  const token = await mouseOverOrFocusAndRecord(t,invokerMethod,invoker);
     86  let showing = popover.matches(':popover-open');
     87  // See NOTE above.
     88  if (msSinceMouseOver(token) < invokerShowDelayMs)
     89    assert_false(showing,'interest should not be shown immediately');
     90  await waitForHoverTime(hoverFocusWaitTimeMs);
     91  assert_true(msSinceMouseOver(token) >= hoverFocusWaitTimeMs,'waitForHoverTime should wait the specified time');
     92  assert_true(popover.matches(':popover-open'),'interest should be shown after the delay');
     93  assert_true(hoverFocusWaitTimeMs > invokerShowDelayMs,'invokerShowDelayMs is the CSS setting, hoverFocusWaitTimeMs should be longer than that');
     94 },`interestfor fires after a delay, ${description}`);
     95 
     96 promise_test(async (t) => {
     97  const {popover,invoker} = await makePopoverAndInvoker(t,invokerLayout);
     98  assert_false(popover.matches(':popover-open'));
     99  invoker.click(); // Click the invoker
    100  assert_false(popover.matches(':popover-open'),'Clicking the invoker should not "show interest"');
    101 },`interestfor should not trigger via mouse click, ${description}`);
    102 
    103 promise_test(async (t) => {
    104  const longerHoverDelay = hoverFocusWaitTimeMs*2;
    105  const {popover,invoker} = await makePopoverAndInvoker(t,invokerLayout,longerHoverDelay);
    106  const token = await mouseOverOrFocusAndRecord(t,invokerMethod,invoker);
    107  await waitForHoverTime(hoverFocusWaitTimeMs);
    108  showing = popover.matches(':popover-open');
    109  if (msSinceMouseOver(token) >= longerHoverDelay)
    110    return; // The WPT runner was too slow.
    111  assert_false(showing,'interestfor should respect CSS setting');
    112 },`interestfor interest-delay-start is respected, ${description}`);
    113 
    114 promise_test(async (t) => {
    115  const longerHoverDelay = hoverFocusWaitTimeMs*4;
    116  const {popover,invoker,unrelated} = await makePopoverAndInvoker(t,invokerLayout,longerHoverDelay);
    117  const token = await mouseOverOrFocusAndRecord(t,invokerMethod,invoker);
    118  await waitForHoverTime(hoverFocusWaitTimeMs);
    119  showing1 = popover.matches(':popover-open');
    120  await hoverOrFocus(invokerMethod,unrelated);
    121  if (msSinceMouseOver(token) >= longerHoverDelay)
    122    return; // The WPT runner was too slow.
    123  await waitForHoverTime(longerHoverDelay);
    124  showing2 = popover.matches(':popover-open');
    125  assert_false(showing1,'interest shouldn\'t be shown immediately');
    126  assert_false(showing2,'because target was de-hovered/de-focused before the delay elapsed, interest should never be shown');
    127 },`Show delay is cancelled if hover/focus changes, ${description}`);
    128 
    129 promise_test(async (t) => {
    130  const {popover,invoker} = await makePopoverAndInvoker(t,invokerLayout);
    131  popover.showPopover();
    132  assert_true(popover.matches(':popover-open'));
    133  let gotInterest = false;
    134  popover.addEventListener('interest',() => (gotInterest=true),{once:true});
    135  await hoverOrFocus(invokerMethod,invoker);
    136  const stillOpen = popover.matches(':popover-open');
    137  await waitForHoverTime(hoverFocusWaitTimeMs);
    138  assert_true(popover.matches(':popover-open'),'popover should stay showing after delay');
    139  assert_true(stillOpen,'popover should have been open before the delay also');
    140  assert_true(gotInterest,'interest event should still be fired');
    141 },`interestfor does not close an already-open popover, ${description}`);
    142 
    143 promise_test(async (t) => {
    144  const {popover,invoker} = await makePopoverAndInvoker(t,invokerLayout);
    145  popover.remove(); // Remove from the document
    146  let gotInterest = false;
    147  popover.addEventListener('interest',() => (gotInterest=true),{once:true});
    148  await hoverOrFocus(invokerMethod,invoker);
    149  await waitForHoverTime(hoverFocusWaitTimeMs);
    150  assert_false(gotInterest,'interest should not be shown if the target is removed');
    151  // Now put it back in the document and make sure it doesn't trigger.
    152  document.body.appendChild(popover);
    153  await waitForHoverTime(hoverFocusWaitTimeMs);
    154  assert_false(gotInterest,'interest should not be shown even when returned to the document');
    155 },`interestfor does nothing when the target is moved out of the document, ${description}`);
    156 
    157 promise_test(async (t) => {
    158  const {popover,invoker,reassignInvokerTargetFn} = await makePopoverAndInvoker(t,invokerLayout);
    159  const popover2 = document.createElement('div');
    160  popover2.popover = 'auto';
    161  document.body.appendChild(popover2);
    162  t.add_cleanup(() => popover2.remove());
    163  const token = await mouseOverOrFocusAndRecord(t,invokerMethod,invoker);
    164  let eitherShowing = popover.matches(':popover-open') || popover2.matches(':popover-open');
    165  reassignInvokerTargetFn(popover2);
    166  // See NOTE above.
    167  if (msSinceMouseOver(token) >= invokerShowDelayMs)
    168    return; // The WPT runner was too slow.
    169  assert_false(eitherShowing,'interest should should not be shown immediately');
    170  await waitForHoverTime(hoverFocusWaitTimeMs);
    171  assert_false(popover.matches(':popover-open'),'old target should not receive interest, since interestfor was reassigned');
    172  assert_false(popover2.matches(':popover-open'),'new target should not receive interest, since interestfor was reassigned');
    173 },`interestfor does nothing when the target changes, ${description}`);
    174 
    175 promise_test(async (t) => {
    176  const {popover,invoker,unrelated} = await makePopoverAndInvoker(t,invokerLayout,/*showdelayMs*/0);
    177  const invoker2 = document.createElement('button');
    178  invoker2.interestForElement = popover;
    179  document.body.appendChild(invoker2);
    180  t.add_cleanup(() => invoker2.remove());
    181  invoker2.setAttribute('style',`
    182    interest-delay-start: 0s;
    183    interest-delay-end: 10000s;
    184    position:fixed;
    185    top:300px;
    186  `);
    187  invoker2.innerText = 'Invoker 2';
    188  let events = [];
    189  popover.addEventListener('interest',() => events.push('interest'));
    190  popover.addEventListener('loseinterest',() => events.push('lose interest'));
    191  popover.addEventListener('beforetoggle',(e) => events.push(e.newState));
    192  assert_array_equals(events,[]);
    193  await hoverOrFocus(invokerMethod,invoker);
    194  assert_array_equals(events,['interest','open']);
    195  // Different invoker for same target - should first (immediately) lose interest on old invoker.
    196  await hoverOrFocus(invokerMethod,invoker2);
    197  assert_array_equals(events,['interest','open','lose interest','closed','interest','open']);
    198  // Lose interest delays are long, so nothing happens here.
    199  events = [];
    200  await hoverOrFocus(invokerMethod,unrelated);
    201  assert_array_equals(events,[]);
    202  // Back to the same invoker - shouldn't re-fire events.
    203  await hoverOrFocus(invokerMethod,invoker2);
    204  assert_array_equals(events,[]);
    205  assert_true(popover.matches(':popover-open'));
    206  popover.hidePopover();
    207 },`moving hover/focus between two invokers for the same target does the right thing, ${description}`);
    208 </script>