tor-browser

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

popover-focus-3.html (9843B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8" />
      3 <title>Popover focus behaviors</title>
      4 <meta name="timeout" content="long">
      5 <link rel="author" title="Luke Warlow" href="mailto:lwarlow@igalia.com">
      6 <link rel=help href="https://open-ui.org/components/popover.research.explainer">
      7 <script src="/resources/testharness.js"></script>
      8 <script src="/resources/testharnessreport.js"></script>
      9 <script src="/resources/testdriver.js"></script>
     10 <script src="/resources/testdriver-actions.js"></script>
     11 <script src="/resources/testdriver-vendor.js"></script>
     12 <script src="resources/popover-utils.js"></script>
     13 
     14 <button id=circular0 tabindex="0">Invoker</button>
     15 <div id=popover4 popover>
     16  <button id=circular1 autofocus tabindex="0"></button>
     17  <button id=circular2 tabindex="0"></button>
     18  <button id=circular3 tabindex="0"></button>
     19 </div>
     20 <button id=circular4 tabindex="0">after</button>
     21 <script>
     22 async function testCircularReferenceTabNavigation() {
     23  circular0.focus();
     24  await sendEnter(); // Activate the invoker
     25  await verifyFocusOrder([circular0, circular1, circular2, circular3, circular4],'circular reference');
     26  popover4.hidePopover();
     27 }
     28 promise_test(async t => {
     29  circular0.setAttribute('popovertarget', 'popover4');
     30  circular1.setAttribute('popovertarget', 'popover4');
     31  circular1.setAttribute('popovertargetaction', 'hide');
     32  circular2.setAttribute('popovertarget', 'popover4');
     33  circular2.setAttribute('popovertargetaction', 'show');
     34  circular3.setAttribute('popovertarget', 'popover4');
     35  t.add_cleanup(() => {
     36    circular0.removeAttribute('popovertarget');
     37    circular1.removeAttribute('popovertarget');
     38    circular1.removeAttribute('popovertargetaction');
     39    circular2.removeAttribute('popovertarget');
     40    circular2.removeAttribute('popovertargetaction');
     41    circular3.removeAttribute('popovertarget');
     42  });
     43  await testCircularReferenceTabNavigation();
     44 }, "Circular reference tab navigation with popovertarget invocation");
     45 promise_test(async t => {
     46  circular0.setAttribute('commandfor', 'popover4');
     47  circular1.setAttribute('commandfor', 'popover4');
     48  circular2.setAttribute('commandfor', 'popover4');
     49  circular3.setAttribute('commandfor', 'popover4');
     50  circular0.setAttribute('command', 'toggle-popover');
     51  circular1.setAttribute('command', 'hide-popover');
     52  circular2.setAttribute('command', 'show-popover');
     53  circular3.setAttribute('command', 'toggle-popover');
     54  t.add_cleanup(() => {
     55    circular0.removeAttribute('commandfor');
     56    circular1.removeAttribute('commandfor');
     57    circular2.removeAttribute('commandfor');
     58    circular3.removeAttribute('commandfor');
     59    circular0.removeAttribute('command');
     60    circular1.removeAttribute('command');
     61    circular2.removeAttribute('command');
     62    circular3.removeAttribute('command');
     63  });
     64  await testCircularReferenceTabNavigation();
     65 }, "Circular reference tab navigation with command/commandfor invocation");
     66 promise_test(async t => {
     67  const circular0Click = () => {
     68    popover4.togglePopover({ source: circular0 });
     69  };
     70  circular0.addEventListener('click', circular0Click);
     71  const circular1Click = () => {
     72    popover4.hidePopover();
     73  };
     74  circular1.addEventListener('click', circular1Click);
     75  const circular2Click = () => {
     76    popover4.showPopover({ source: circular2 });
     77  };
     78  circular2.addEventListener('click', circular2Click);
     79  const circular3Click = () => {
     80    popover4.togglePopover({ source: circular3 });
     81  };
     82  circular3.addEventListener('click', circular3Click);
     83  t.add_cleanup(() => {
     84    circular0.removeEventListener('click', circular0Click);
     85    circular1.removeEventListener('click', circular1Click);
     86    circular2.removeEventListener('click', circular2Click);
     87    circular3.removeEventListener('click', circular3Click);
     88  });
     89  await testCircularReferenceTabNavigation();
     90 }, "Circular reference tab navigation with imperative invocation");
     91 </script>
     92 
     93 <div id=focus-return1>
     94  <button tabindex="0">Show popover</button>
     95  <div popover id=focus-return1-p>
     96    <button autofocus tabindex="0">Hide popover</button>
     97  </div>
     98 </div>
     99 <script>
    100 async function testPopoverFocusReturn1() {
    101  const invoker = document.querySelector('#focus-return1>button');
    102  const popover = document.querySelector('#focus-return1>[popover]');
    103  const hideButton = popover.querySelector('button');
    104  invoker.focus(); // Make sure button is focused.
    105  assert_equals(document.activeElement,invoker);
    106  await sendEnter(); // Activate the invoker
    107  assert_true(popover.matches(':popover-open'), 'popover should be invoked by invoker');
    108  assert_equals(document.activeElement,hideButton,'Hide button should be focused due to autofocus attribute');
    109  await sendEnter(); // Activate the hide invoker
    110  assert_false(popover.matches(':popover-open'), 'popover should be hidden by invoker');
    111  assert_equals(document.activeElement,invoker,'Focus should be returned to the invoker');
    112 }
    113 promise_test(async t => {
    114  const invoker = document.querySelector('#focus-return1>button');
    115  const popover = document.querySelector('#focus-return1>[popover]');
    116  const hideButton = popover.querySelector('button');
    117  invoker.setAttribute('popovertarget', 'focus-return1-p');
    118  invoker.setAttribute('popovertargetaction', 'show');
    119  hideButton.setAttribute('popovertarget', 'focus-return1-p');
    120  hideButton.setAttribute('popovertargetaction', 'hide');
    121  t.add_cleanup(() => {
    122    invoker.removeAttribute('popovertarget');
    123    invoker.removeAttribute('popovertargetaction');
    124    hideButton.removeAttribute('popovertarget');
    125    hideButton.removeAttribute('popovertargetaction');
    126  });
    127  await testPopoverFocusReturn1();
    128 }, "Popover focus returns when popover is hidden by invoker with popovertarget invocation");
    129 promise_test(async t => {
    130  const invoker = document.querySelector('#focus-return1>button');
    131  const popover = document.querySelector('#focus-return1>[popover]');
    132  const hideButton = popover.querySelector('button');
    133  invoker.setAttribute('commandfor', 'focus-return1-p');
    134  invoker.setAttribute('command', 'show-popover');
    135  hideButton.setAttribute('commandfor', 'focus-return1-p');
    136  hideButton.setAttribute('command', 'hide-popover');
    137  t.add_cleanup(() => {
    138    invoker.removeAttribute('commandfor');
    139    invoker.removeAttribute('command');
    140    hideButton.removeAttribute('commandfor');
    141    hideButton.removeAttribute('command');
    142  });
    143  await testPopoverFocusReturn1();
    144 }, "Popover focus returns when popover is hidden by invoker with commandfor invocation");
    145 promise_test(async t => {
    146  const invoker = document.querySelector('#focus-return1>button');
    147  const popover = document.querySelector('#focus-return1>[popover]');
    148  const hideButton = popover.querySelector('button');
    149  const invokerClick = () => {
    150    popover.showPopover({ source: invoker });
    151  };
    152  invoker.addEventListener('click', invokerClick);
    153  const hideButtonClick = () => {
    154    popover.hidePopover();
    155  };
    156  hideButton.addEventListener('click', hideButtonClick);
    157  t.add_cleanup(() => {
    158    invoker.removeEventListener('click', invokerClick);
    159    hideButton.removeEventListener('click', hideButtonClick);
    160  });
    161  await testPopoverFocusReturn1();
    162 }, "Popover focus returns when popover is hidden by invoker with imperative invocation");
    163 </script>
    164 
    165 <div id=focus-return2>
    166  <button tabindex="0">Toggle popover</button>
    167  <div popover id=focus-return2-p>Popover with <button tabindex="0">focusable element</button></div>
    168  <span tabindex=0>Other focusable element</span>
    169 </div>
    170 <script>
    171 async function testPopoverFocusReturn2() {
    172  const invoker = document.querySelector('#focus-return2>button');
    173  const popover = document.querySelector('#focus-return2>[popover]');
    174  const otherElement = document.querySelector('#focus-return2>span');
    175  invoker.focus(); // Make sure button is focused.
    176  assert_equals(document.activeElement,invoker);
    177  invoker.click(); // Activate the invoker
    178  assert_true(popover.matches(':popover-open'), 'popover should be invoked by invoker');
    179  assert_equals(document.activeElement,invoker,'invoker should still be focused');
    180  await sendTab();
    181  assert_equals(document.activeElement,popover.querySelector('button'),'next up is the popover');
    182  await sendTab();
    183  assert_equals(document.activeElement,otherElement,'next focus stop is outside the popover');
    184  await sendEscape(); // Close the popover via ESC
    185  assert_false(popover.matches(':popover-open'), 'popover should be hidden');
    186  assert_equals(document.activeElement,otherElement,'focus does not move because it was not inside the popover');
    187 }
    188 promise_test(async t => {
    189  const invoker = document.querySelector('#focus-return2>button');
    190  invoker.setAttribute('popovertarget', 'focus-return2-p');
    191  t.add_cleanup(() => {
    192    invoker.removeAttribute('popovertarget');
    193  });
    194  await testPopoverFocusReturn2();
    195 }, "Popover focus only returns to invoker when focus is within the popover with popovertarget invocation");
    196 promise_test(async t => {
    197  const invoker = document.querySelector('#focus-return2>button');
    198  invoker.setAttribute('command', 'toggle-popover');
    199  invoker.setAttribute('commandfor', 'focus-return2-p');
    200  t.add_cleanup(() => {
    201    invoker.removeAttribute('command');
    202    invoker.removeAttribute('commandfor');
    203  });
    204  await testPopoverFocusReturn2();
    205 }, "Popover focus only returns to invoker when focus is within the popover with command/commandfor invocation");
    206 promise_test(async t => {
    207  const invoker = document.querySelector('#focus-return2>button');
    208  const popover = document.querySelector('#focus-return2>[popover]');
    209  const invokerClick = () => {
    210    popover.togglePopover({ source: invoker });
    211  };
    212  invoker.addEventListener('click', invokerClick);
    213  t.add_cleanup(() => {
    214    invoker.removeEventListener('click', invokerClick);
    215  });
    216  await testPopoverFocusReturn2();
    217 }, "Popover focus only returns to invoker when focus is within the popover with imperative invocation");
    218 </script>