tor-browser

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

popover-focus-2.html (7855B)


      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 <meta name=variant content=?test1>
     15 <meta name=variant content=?test2>
     16 <meta name=variant content=?test3>
     17 
     18 <div id=fixup>
     19  <button id=button1 tabindex="0">Button1</button>
     20  <div popover id=popover0  tabindex="0" style="top:300px">
     21  </div>
     22  <div popover id=popover1 style="top:100px">
     23    <button id=inside_popover1 tabindex="0">Inside1</button>
     24    <button id=invoker2 tabindex="0">Nested Invoker 2</button>
     25    <button id=inside_popover2 tabindex="0">Inside2</button>
     26  </div>
     27  <button id=button2 tabindex="0">Button2</button>
     28  <div popover id=popover_no_invoker tabindex="0" style="top:300px"></div>
     29  <button id=invoker0 tabindex="0">Invoker0</button>
     30  <button id=invoker1 tabindex="0">Invoker1</button>
     31  <button id=button3 tabindex="0">Button3</button>
     32  <div popover id=popover2 style="top:200px">
     33    <button id=inside_popover3 tabindex="0">Inside3</button>
     34    <button id=invoker3 tabindex="0">Nested Invoker 3</button>
     35  </div>
     36  <div popover id=popover3 style="top:300px">
     37    Non-focusable popover
     38  </div>
     39  <button id=button4 tabindex="0">Button4</button>
     40 </div>
     41 <style>
     42  #fixup [popover] {
     43    bottom:auto;
     44  }
     45 </style>
     46 <script>
     47 async function testPopoverFocusNavigation() {
     48  button1.focus();
     49  assert_equals(document.activeElement,button1);
     50  await sendTab();
     51  assert_equals(document.activeElement,button2,'Hidden popover should be skipped');
     52  await sendShiftTab();
     53  assert_equals(document.activeElement,button1,'Hidden popover should be skipped backwards');
     54  popover_no_invoker.showPopover();
     55  await sendTab();
     56  await sendTab();
     57  assert_equals(document.activeElement,popover_no_invoker,"Focusable popover that is opened without an invoker should get focused");
     58  await sendTab();
     59  assert_equals(document.activeElement,invoker0);
     60  await sendEnter(); // Activate the invoker0
     61  assert_true(popover0.matches(':popover-open'), 'popover0 should be invoked by invoker0');
     62  assert_equals(document.activeElement,invoker0,'Focus should not move when popover is shown');
     63  await sendTab();
     64  await sendEnter(); // Activate the invoker
     65  assert_true(popover1.matches(':popover-open'), 'popover1 should be invoked by invoker1');
     66  assert_equals(document.activeElement,invoker1,'Focus should not move when popover is shown');
     67  await sendTab();
     68  // Make invoker1 non-focusable.
     69  invoker1.disabled = true;
     70  assert_equals(document.activeElement,inside_popover1,'Focus should move from invoker into the open popover');
     71  await sendTab();
     72  assert_equals(document.activeElement,invoker2,'Focus should move within popover');
     73  await sendShiftTab();
     74  await sendShiftTab();
     75  assert_equals(document.activeElement,invoker0,'Focus should not move back to invoker as it is non-focusable');
     76  // Reset invoker1 to focusable.
     77  invoker1.disabled = false;
     78  await verifyFocusOrder([button1, button2, invoker0, invoker1, inside_popover1, invoker2, inside_popover2, button3, button4],'set 1');
     79  invoker2.focus();
     80  await sendEnter(); // Activate the nested invoker
     81  assert_true(popover2.matches(':popover-open'), 'popover2 should be invoked by nested invoker');
     82  assert_equals(document.activeElement,invoker2,'Focus should stay on the invoker');
     83  await sendTab();
     84  assert_equals(document.activeElement,inside_popover3,'Focus should move into nested popover');
     85  await sendTab();
     86  assert_equals(document.activeElement,invoker3);
     87  await sendEnter(); // Activate the (empty) nested invoker
     88  assert_true(popover3.matches(':popover-open'), 'popover3 should be invoked by nested invoker');
     89  assert_equals(document.activeElement,invoker3,'Focus should stay on the invoker');
     90  await sendTab();
     91  assert_equals(document.activeElement,inside_popover2,'Focus should skip popover without focusable content, going back to higher scope');
     92  await sendShiftTab();
     93  assert_equals(document.activeElement,invoker3,'Shift-tab from the higher scope should return to the lower scope');
     94  await sendTab();
     95  assert_equals(document.activeElement,inside_popover2);
     96  await sendTab();
     97  assert_equals(document.activeElement,button3,'Focus should exit popovers');
     98  await sendTab();
     99  assert_equals(document.activeElement,button4,'Focus should skip popovers');
    100  button1.focus();
    101  await verifyFocusOrder([button1, button2, invoker0, invoker1, inside_popover1, invoker2, inside_popover3, invoker3, inside_popover2, button3, button4],'set 2');
    102 }
    103 
    104 // This test is very slow. Variants are used to split it into pieces.
    105 switch (window.location.search.substring(1)) {
    106  case 'test1':
    107    promise_test(async t => {
    108      invoker0.setAttribute('popovertarget', 'popover0');
    109      invoker1.setAttribute('popovertarget', 'popover1');
    110      invoker2.setAttribute('popovertarget', 'popover2');
    111      invoker3.setAttribute('popovertarget', 'popover3');
    112      t.add_cleanup(() => {
    113        invoker0.removeAttribute('popovertarget');
    114        invoker1.removeAttribute('popovertarget');
    115        invoker2.removeAttribute('popovertarget');
    116        invoker3.removeAttribute('popovertarget');
    117      });
    118      await testPopoverFocusNavigation();
    119    }, "Popover focus navigation with popovertarget invocation");
    120    break;
    121  case 'test2':
    122    promise_test(async t => {
    123      invoker0.setAttribute('commandfor', 'popover0');
    124      invoker1.setAttribute('commandfor', 'popover1');
    125      invoker2.setAttribute('commandfor', 'popover2');
    126      invoker3.setAttribute('commandfor', 'popover3');
    127      invoker0.setAttribute('command', 'toggle-popover');
    128      invoker1.setAttribute('command', 'toggle-popover');
    129      invoker2.setAttribute('command', 'toggle-popover');
    130      invoker3.setAttribute('command', 'toggle-popover');
    131      t.add_cleanup(() => {
    132        invoker0.removeAttribute('commandfor');
    133        invoker1.removeAttribute('commandfor');
    134        invoker2.removeAttribute('commandfor');
    135        invoker3.removeAttribute('commandfor');
    136        invoker0.removeAttribute('command');
    137        invoker1.removeAttribute('command');
    138        invoker2.removeAttribute('command');
    139        invoker3.removeAttribute('command');
    140      });
    141      await testPopoverFocusNavigation();
    142    }, "Popover focus navigation with command/commandfor invocation");
    143    break;
    144  case 'test3':
    145    promise_test(async t => {
    146      const invoker0Click = () => {
    147        popover0.togglePopover({ source: invoker0 });
    148      };
    149      invoker0.addEventListener('click', invoker0Click);
    150      const invoker1Click = () => {
    151        popover1.togglePopover({ source: invoker1 });
    152      };
    153      invoker1.addEventListener('click', invoker1Click);
    154      const invoker2Click = () => {
    155        popover2.togglePopover({ source: invoker2 });
    156      };
    157      invoker2.addEventListener('click', invoker2Click);
    158      const invoker3Click = () => {
    159        popover3.togglePopover({ source: invoker3 });
    160      };
    161      invoker3.addEventListener('click', invoker3Click);
    162      t.add_cleanup(() => {
    163        invoker0.removeEventListener('click', invoker0Click);
    164        invoker1.removeEventListener('click', invoker1Click);
    165        invoker2.removeEventListener('click', invoker2Click);
    166        invoker3.removeEventListener('click', invoker3Click);
    167      });
    168      await testPopoverFocusNavigation()
    169    }, "Popover focus navigation with imperative invocation");
    170    break;
    171  default:
    172    assert_unreached('Invalid variant');
    173 }
    174 </script>