tor-browser

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

popover-light-dismiss-command.html (15771B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8" />
      3 <title>Popover light dismiss behavior with command/commandfor</title>
      4 <meta name="timeout" content="long">
      5 <link rel="author" href="mailto:masonf@chromium.org">
      6 <link rel="author" href="mailto:lwarlow@igalia.com">
      7 <link rel=help href="https://open-ui.org/components/popover.research.explainer">
      8 <script src="/resources/testharness.js"></script>
      9 <script src="/resources/testharnessreport.js"></script>
     10 <script src="/resources/testdriver.js"></script>
     11 <script src="/resources/testdriver-actions.js"></script>
     12 <script src="/resources/testdriver-vendor.js"></script>
     13 <script src="resources/popover-utils.js"></script>
     14 <style>
     15  [popover] {
     16    /* Position most popovers at the bottom-right, out of the way */
     17    inset:auto;
     18    bottom:0;
     19    right:0;
     20  }
     21  [popover]::backdrop {
     22    /* This should *not* affect anything: */
     23    pointer-events: auto;
     24  }
     25 </style>
     26 <button id=b1t commandfor='p1' command="toggle-popover">Popover 1</button>
     27 <button id=b1s commandfor='p1' command="show-popover">Popover 1</button>
     28 <span id=outside>Outside all popovers</span>
     29 <div popover id=p1>
     30  <span id=inside1>Inside popover 1</span>
     31  <button id=b2 commandfor='p2' command="show-popover">Popover 2</button>
     32  <span id=inside1after>Inside popover 1 after button</span>
     33  <div popover id=p2>
     34    <span id=inside2>Inside popover 2</span>
     35  </div>
     36 </div>
     37 <button id=after_p1 tabindex="0">Next control after popover1</button>
     38 <style>
     39  #p1 {top: 50px;}
     40  #p2 {top: 120px;}
     41 </style>
     42 <script>
     43  const popover1 = document.querySelector('#p1');
     44  const button1toggle = document.querySelector('#b1t');
     45  const button1show = document.querySelector('#b1s');
     46  const inside1After = document.querySelector('#inside1after');
     47  const button2 = document.querySelector('#b2');
     48  const popover2 = document.querySelector('#p2');
     49  const outside = document.querySelector('#outside');
     50  const inside1 = document.querySelector('#inside1');
     51  const inside2 = document.querySelector('#inside2');
     52  const afterp1 = document.querySelector('#after_p1');
     53  let popover1HideCount = 0;
     54  popover1.addEventListener('beforetoggle',(e) => {
     55    if (e.newState !== "closed")
     56      return;
     57    ++popover1HideCount;
     58    e.preventDefault(); // 'beforetoggle' should not be cancellable.
     59  });
     60  let popover2HideCount = 0;
     61  popover2.addEventListener('beforetoggle',(e) => {
     62    if (e.newState !== "closed")
     63      return;
     64    ++popover2HideCount;
     65    e.preventDefault(); // 'beforetoggle' should not be cancellable.
     66  });
     67  promise_test(async () => {
     68    await clickOn(button1show);
     69    assert_true(popover1.matches(':popover-open'));
     70    await waitForRender();
     71    p1HideCount = popover1HideCount;
     72    await clickOn(button1show);
     73    assert_true(popover1.matches(':popover-open'),'popover1 should stay open');
     74    assert_equals(popover1HideCount,p1HideCount,'popover1 should not get hidden and reshown');
     75    popover1.hidePopover(); // Cleanup
     76    assert_false(popover1.matches(':popover-open'));
     77  },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover');
     78  promise_test(async () => {
     79    popover1.showPopover();
     80    assert_true(popover1.matches(':popover-open'));
     81    assert_false(popover2.matches(':popover-open'));
     82    await clickOn(button2);
     83    assert_true(popover2.matches(':popover-open'),'button2 should activate popover2');
     84    p2HideCount = popover2HideCount;
     85    await clickOn(button2);
     86    assert_true(popover2.matches(':popover-open'),'popover2 should stay open');
     87    assert_equals(popover2HideCount,p2HideCount,'popover2 should not get hidden and reshown');
     88    popover1.hidePopover(); // Cleanup
     89    assert_false(popover1.matches(':popover-open'));
     90    assert_false(popover2.matches(':popover-open'));
     91  },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover (nested case)');
     92  promise_test(async () => {
     93    popover1.showPopover();
     94    popover2.showPopover();
     95    assert_true(popover1.matches(':popover-open'));
     96    assert_true(popover2.matches(':popover-open'));
     97    p2HideCount = popover2HideCount;
     98    await clickOn(button2);
     99    assert_true(popover2.matches(':popover-open'),'popover2 should stay open');
    100    assert_equals(popover2HideCount,p2HideCount,'popover2 should not get hidden and reshown');
    101    popover1.hidePopover(); // Cleanup
    102    assert_false(popover1.matches(':popover-open'));
    103    assert_false(popover2.matches(':popover-open'));
    104  },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover (nested case, not used for invocation)');
    105  promise_test(async () => {
    106    popover1.showPopover(); // Directly show the popover
    107    assert_true(popover1.matches(':popover-open'));
    108    await waitForRender();
    109    p1HideCount = popover1HideCount;
    110    await clickOn(button1show);
    111    assert_true(popover1.matches(':popover-open'),'popover1 should stay open');
    112    assert_equals(popover1HideCount,p1HideCount,'popover1 should not get hidden and reshown');
    113    popover1.hidePopover(); // Cleanup
    114    assert_false(popover1.matches(':popover-open'));
    115  },'Clicking on invoking element, even if it wasn\'t used for activation, shouldn\'t close its popover');
    116  promise_test(async () => {
    117    popover1.showPopover(); // Directly show the popover
    118    assert_true(popover1.matches(':popover-open'));
    119    await waitForRender();
    120    p1HideCount = popover1HideCount;
    121    await clickOn(button1toggle);
    122    assert_false(popover1.matches(':popover-open'),'popover1 should be hidden by command/commandfor');
    123    assert_equals(popover1HideCount,p1HideCount+1,'popover1 should get hidden only once by command/commandfor');
    124  },'Clicking on command/commandfor element, even if it wasn\'t used for activation, should hide it exactly once');
    125 </script>
    126 <button id=b3 commandfor=p3 command="toggle-popover">Popover 3 - button 3
    127  <div popover id=p4>Inside popover 4</div>
    128 </button>
    129 <div popover id=p3>Inside popover 3</div>
    130 <div popover id=p5>Inside popover 5
    131  <button commandfor=p3 command="toggle-popover">Popover 3 - button 4 - unused</button>
    132 </div>
    133 <style>
    134  #p3 {top:100px;}
    135  #p4 {top:200px;}
    136  #p5 {top:200px;}
    137 </style>
    138 <script>
    139  const popover3 = document.querySelector('#p3');
    140  const popover4 = document.querySelector('#p4');
    141  const popover5 = document.querySelector('#p5');
    142  const button3 = document.querySelector('#b3');
    143  promise_test(async () => {
    144    await clickOn(button3);
    145    assert_true(popover3.matches(':popover-open'),'invoking element should open popover');
    146    popover4.showPopover();
    147    assert_true(popover4.matches(':popover-open'));
    148    assert_false(popover3.matches(':popover-open'),'popover3 is unrelated to popover4');
    149    popover4.hidePopover(); // Cleanup
    150    assert_false(popover4.matches(':popover-open'));
    151  },'A popover inside an invoking element doesn\'t participate in that invoker\'s ancestor chain');
    152  promise_test(async () => {
    153    popover5.showPopover();
    154    assert_true(popover5.matches(':popover-open'));
    155    assert_false(popover3.matches(':popover-open'));
    156    popover3.showPopover();
    157    assert_true(popover3.matches(':popover-open'));
    158    assert_false(popover5.matches(':popover-open'),'Popover 5 was not invoked from popover3\'s invoker');
    159    popover3.hidePopover();
    160    assert_false(popover3.matches(':popover-open'));
    161  },'An invoking element that was not used to invoke the popover is not part of the ancestor chain');
    162 </script>
    163 <my-element id="myElement">
    164  <template shadowrootmode="open">
    165    <button id=b7 commandfor=p7 command=show-popover tabindex="0">Popover7</button>
    166    <div popover id=p7 style="top: 100px;">
    167      <p>Popover content.</p>
    168      <input id="inside7" type="text" placeholder="some text">
    169    </div>
    170  </template>
    171 </my-element>
    172 <script>
    173  const button7 = document.querySelector('#myElement').shadowRoot.querySelector('#b7');
    174  const popover7 = document.querySelector('#myElement').shadowRoot.querySelector('#p7');
    175  const inside7 = document.querySelector('#myElement').shadowRoot.querySelector('#inside7');
    176  promise_test(async () => {
    177    button7.click();
    178    assert_true(popover7.matches(':popover-open'),'invoking element should open popover');
    179    inside7.click();
    180    assert_true(popover7.matches(':popover-open'));
    181    popover7.hidePopover();
    182  },'Clicking inside a shadow DOM popover does not close that popover');
    183  promise_test(async () => {
    184    button7.click();
    185    inside7.click();
    186    assert_true(popover7.matches(':popover-open'));
    187    await clickOn(outside);
    188    assert_false(popover7.matches(':popover-open'));
    189  },'Clicking outside a shadow DOM popover should close that popover');
    190 </script>
    191 <div popover id=p8>
    192  <button tabindex="0">Button</button>
    193  <span id=inside8after>Inside popover 8 after button</span>
    194 </div>
    195 <button id=p8invoker commandfor=p8 command="toggle-popover" tabindex="0">Popover8 invoker (no action)</button>
    196 <script>
    197  promise_test(async () => {
    198    const popover8 = document.querySelector('#p8');
    199    const inside8After = document.querySelector('#inside8after');
    200    const popover8Invoker = document.querySelector('#p8invoker');
    201    assert_false(popover8.matches(':popover-open'));
    202    popover8.showPopover();
    203    await clickOn(inside8After);
    204    assert_true(popover8.matches(':popover-open'));
    205    await sendTab();
    206    assert_equals(document.activeElement,popover8Invoker,'Focus should move to the invoker element');
    207    assert_true(popover8.matches(':popover-open'),'popover should stay open');
    208    popover8.hidePopover(); // Cleanup
    209  },'Moving focus back to the invoker element should not dismiss the popover');
    210 </script>
    211 <!-- Convoluted ancestor relationship -->
    212 <div popover id=convoluted_p1>Popover 1
    213  <button commandfor=convoluted_p2 command="toggle-popover">Open Popover 2</button>
    214 <div popover id=convoluted_p2>Popover 2
    215    <button commandfor=convoluted_p3 command="toggle-popover">Open Popover 3</button>
    216    <button commandfor=convoluted_p2 command=show-popover>Self-linked invoker</button>
    217  </div>
    218  <div popover id=convoluted_p3>Popover 3
    219    <button commandfor=convoluted_p4 command="toggle-popover">Open Popover 4</button>
    220  </div>
    221  <div popover id=convoluted_p4><p>Popover 4</p></div>
    222 </div>
    223 <button onclick="convoluted_p1.showPopover()" tabindex="0">Open convoluted popover</button>
    224 <style>
    225  #convoluted_p1 {top:50px;}
    226  #convoluted_p2 {top:150px;}
    227  #convoluted_p3 {top:250px;}
    228  #convoluted_p4 {top:350px;}
    229 </style>
    230 <script>
    231 const convPopover1 = document.querySelector('#convoluted_p1');
    232 const convPopover2 = document.querySelector('#convoluted_p2');
    233 const convPopover3 = document.querySelector('#convoluted_p3');
    234 const convPopover4 = document.querySelector('#convoluted_p4');
    235 promise_test(async () => {
    236  convPopover1.showPopover(); // Programmatically open p1
    237  assert_true(convPopover1.matches(':popover-open'));
    238  convPopover1.querySelector('button').click(); // Click to invoke p2
    239  assert_true(convPopover1.matches(':popover-open'));
    240  assert_true(convPopover2.matches(':popover-open'));
    241  convPopover2.querySelector('button').click(); // Click to invoke p3
    242  assert_true(convPopover1.matches(':popover-open'));
    243  assert_true(convPopover2.matches(':popover-open'));
    244  assert_true(convPopover3.matches(':popover-open'));
    245  convPopover3.querySelector('button').click(); // Click to invoke p4
    246  assert_true(convPopover1.matches(':popover-open'));
    247  assert_true(convPopover2.matches(':popover-open'));
    248  assert_true(convPopover3.matches(':popover-open'));
    249  assert_true(convPopover4.matches(':popover-open'));
    250  convPopover4.firstElementChild.click(); // Click within p4
    251  assert_true(convPopover1.matches(':popover-open'));
    252  assert_true(convPopover2.matches(':popover-open'));
    253  assert_true(convPopover3.matches(':popover-open'));
    254  assert_true(convPopover4.matches(':popover-open'));
    255  convPopover1.hidePopover();
    256  assert_false(convPopover1.matches(':popover-open'));
    257  assert_false(convPopover2.matches(':popover-open'));
    258  assert_false(convPopover3.matches(':popover-open'));
    259  assert_false(convPopover4.matches(':popover-open'));
    260 },'Ensure circular/convoluted ancestral relationships are functional');
    261 promise_test(async () => {
    262  convPopover1.showPopover(); // Programmatically open p1
    263  convPopover1.querySelector('button').click(); // Click to invoke p2
    264  assert_true(convPopover1.matches(':popover-open'));
    265  assert_true(convPopover2.matches(':popover-open'));
    266  assert_false(convPopover3.matches(':popover-open'));
    267  assert_false(convPopover4.matches(':popover-open'));
    268  convPopover4.showPopover(); // Programmatically open p4
    269  assert_true(convPopover1.matches(':popover-open'),'popover1 stays open because it is a DOM ancestor of popover4');
    270  assert_false(convPopover2.matches(':popover-open'),'popover2 closes because it isn\'t connected to popover4 via active invokers');
    271  assert_true(convPopover4.matches(':popover-open'));
    272  convPopover4.firstElementChild.click(); // Click within p4
    273  assert_true(convPopover1.matches(':popover-open'),'nothing changes');
    274  assert_false(convPopover2.matches(':popover-open'));
    275  assert_true(convPopover4.matches(':popover-open'));
    276  convPopover1.hidePopover();
    277  assert_false(convPopover1.matches(':popover-open'));
    278  assert_false(convPopover2.matches(':popover-open'));
    279  assert_false(convPopover3.matches(':popover-open'));
    280  assert_false(convPopover4.matches(':popover-open'));
    281 },'Ensure circular/convoluted ancestral relationships are functional, with a direct showPopover()');
    282 </script>
    283 <div id=p29 popover>Popover 29</div>
    284 <button id=b29 commandfor=p29 command="toggle-popover">Open popover 29</button>
    285 <iframe id=iframe29 width=100 height=30></iframe>
    286 <script>
    287 promise_test(async () => {
    288  let iframe_url = (new URL("/common/blank.html", location.href)).href;
    289  iframe29.src = iframe_url;
    290  iframe29.contentDocument.body.style.height = '100%';
    291  assert_false(p29.matches(':popover-open'),'initially hidden');
    292  p29.showPopover();
    293  assert_true(p29.matches(':popover-open'),'showing');
    294  let actions = new test_driver.Actions();
    295  // Using the iframe's contentDocument as the origin would throw an error, so
    296  // we are using iframe29 as the origin instead.
    297  const iframe_box = iframe29.getBoundingClientRect();
    298  await actions
    299      .pointerMove(1,1,{origin: b29})
    300      .pointerDown({button: actions.ButtonType.LEFT})
    301      .pointerMove(iframe_box.width / 2, iframe_box.height / 2, {origin: iframe29})
    302      .pointerUp({button: actions.ButtonType.LEFT})
    303      .send();
    304  assert_true(p29.matches(':popover-open'), 'popover should be open after pointerUp in iframe.');
    305  actions = new test_driver.Actions();
    306  await actions
    307      .pointerMove(iframe_box.width / 2, iframe_box.height / 2, {origin: iframe29})
    308      .pointerDown({button: actions.ButtonType.LEFT})
    309      .pointerMove(1,1,{origin: b29})
    310      .pointerUp({button: actions.ButtonType.LEFT})
    311      .send();
    312  assert_true(p29.matches(':popover-open'), 'popover should be open after pointerUp on main frame button.');
    313 },`Pointer down in one document and pointer up in another document shouldn't dismiss popover`);
    314 </script>
    315 <div id=p30 popover>Popover 30</div>
    316 <button id=b30 commandfor=p30 command="toggle-popover">Open popover 30</button>
    317 <button id=b30b>Non-invoker</button>
    318 <script>
    319 promise_test(async () => {
    320  assert_false(p30.matches(':popover-open'),'initially hidden');
    321  p30.showPopover();
    322  assert_true(p30.matches(':popover-open'),'showing');
    323  let actions = new test_driver.Actions();
    324  await actions
    325      .pointerMove(2,2,{origin: b30})
    326      .pointerDown({button: actions.ButtonType.LEFT})
    327      .pointerMove(2,2,{origin: b30b})
    328      .pointerUp({button: actions.ButtonType.LEFT})
    329      .send();
    330  await waitForRender();
    331  assert_true(p30.matches(':popover-open'),'showing after pointerup');
    332 },`Pointer down inside invoker and up outside that invoker shouldn't dismiss popover`);
    333 </script>