tor-browser

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

popover-light-dismiss.html (25667B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8" />
      3 <title>Popover light dismiss behavior</title>
      4 <meta name="timeout" content="long">
      5 <link rel="author" href="mailto:masonf@chromium.org">
      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 <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 
     27 <button id=b1t popovertarget='p1'>Popover 1</button>
     28 <button id=b1s popovertarget='p1' popovertargetaction=show>Popover 1</button>
     29 <span id=outside>Outside all popovers</span>
     30 <div popover id=p1>
     31  <span id=inside1>Inside popover 1</span>
     32  <button id=b2 popovertarget='p2' popovertargetaction=show>Popover 2</button>
     33  <span id=inside1after>Inside popover 1 after button</span>
     34  <div popover id=p2>
     35    <span id=inside2>Inside popover 2</span>
     36  </div>
     37 </div>
     38 <button id=after_p1 tabindex="0">Next control after popover1</button>
     39 <style>
     40  #p1 {top: 50px;}
     41  #p2 {top: 120px;}
     42 </style>
     43 <script>
     44  const popover1 = document.querySelector('#p1');
     45  const button1toggle = document.querySelector('#b1t');
     46  const button1show = document.querySelector('#b1s');
     47  const inside1After = document.querySelector('#inside1after');
     48  const button2 = document.querySelector('#b2');
     49  const popover2 = document.querySelector('#p2');
     50  const outside = document.querySelector('#outside');
     51  const inside1 = document.querySelector('#inside1');
     52  const inside2 = document.querySelector('#inside2');
     53  const afterp1 = document.querySelector('#after_p1');
     54 
     55  let popover1HideCount = 0;
     56  popover1.addEventListener('beforetoggle',(e) => {
     57    if (e.newState !== "closed")
     58      return;
     59    ++popover1HideCount;
     60    e.preventDefault(); // 'beforetoggle' should not be cancellable.
     61  });
     62  let popover2HideCount = 0;
     63  popover2.addEventListener('beforetoggle',(e) => {
     64    if (e.newState !== "closed")
     65      return;
     66    ++popover2HideCount;
     67    e.preventDefault(); // 'beforetoggle' should not be cancellable.
     68  });
     69  promise_test(async () => {
     70    assert_false(popover1.matches(':popover-open'));
     71    popover1.showPopover();
     72    assert_true(popover1.matches(':popover-open'));
     73    let p1HideCount = popover1HideCount;
     74    await clickOn(outside);
     75    assert_false(popover1.matches(':popover-open'));
     76    assert_equals(popover1HideCount,p1HideCount+1);
     77  },'Clicking outside a popover will dismiss the popover');
     78 
     79  promise_test(async (t) => {
     80    const controller = new AbortController();
     81    t.add_cleanup(() => controller.abort());
     82    function addListener(eventName) {
     83      document.addEventListener(eventName,(e) => e.preventDefault(),{signal:controller.signal,capture: true});
     84    }
     85    addListener('pointerdown');
     86    addListener('pointerup');
     87    addListener('mousedown');
     88    addListener('mouseup');
     89    assert_false(popover1.matches(':popover-open'));
     90    popover1.showPopover();
     91    assert_true(popover1.matches(':popover-open'));
     92    let p1HideCount = popover1HideCount;
     93    await clickOn(outside);
     94    assert_false(popover1.matches(':popover-open'),'preventDefault should not prevent light dismiss');
     95    assert_equals(popover1HideCount,p1HideCount+1);
     96  },'Canceling pointer events should not keep clicks from light dismissing popovers');
     97 
     98  promise_test(async () => {
     99    assert_false(popover1.matches(':popover-open'));
    100    popover1.showPopover();
    101    await waitForRender();
    102    p1HideCount = popover1HideCount;
    103    await clickOn(inside1);
    104    assert_true(popover1.matches(':popover-open'));
    105    assert_equals(popover1HideCount,p1HideCount);
    106    popover1.hidePopover();
    107  },'Clicking inside a popover does not close that popover');
    108 
    109  promise_test(async () => {
    110    assert_false(popover1.matches(':popover-open'));
    111    popover1.showPopover();
    112    await waitForRender();
    113    assert_true(popover1.matches(':popover-open'));
    114    await new test_driver.Actions()
    115      .pointerMove(0, 0, {origin: outside})
    116      .pointerDown()
    117      .send();
    118    await waitForRender();
    119    assert_true(popover1.matches(':popover-open'),'pointerdown (outside the popover) should not hide the popover');
    120    await new test_driver.Actions()
    121      .pointerUp()
    122      .send();
    123    await waitForRender();
    124    assert_false(popover1.matches(':popover-open'),'pointerup (outside the popover) should trigger light dismiss');
    125  },'Popovers close on pointerup, not pointerdown');
    126 
    127  promise_test(async (t) => {
    128    t.add_cleanup(() => popover1.hidePopover());
    129    assert_false(popover1.matches(':popover-open'));
    130    popover1.showPopover();
    131    assert_true(popover1.matches(':popover-open'));
    132    async function testOne(eventName) {
    133      document.body.dispatchEvent(new PointerEvent(eventName));
    134      document.body.dispatchEvent(new MouseEvent(eventName));
    135      document.body.dispatchEvent(new ProgressEvent(eventName));
    136      await waitForRender();
    137      assert_true(popover1.matches(':popover-open'),`A synthetic "${eventName}" event should not hide the popover`);
    138    }
    139    await testOne('pointerup');
    140    await testOne('pointerdown');
    141    await testOne('mouseup');
    142    await testOne('mousedown');
    143  },'Synthetic events can\'t close popovers');
    144 
    145  promise_test(async (t) => {
    146    t.add_cleanup(() => popover1.hidePopover());
    147    popover1.showPopover();
    148    await clickOn(inside1After);
    149    assert_true(popover1.matches(':popover-open'));
    150    await sendTab();
    151    assert_equals(document.activeElement,afterp1,'Focus should move to a button outside the popover');
    152    assert_true(popover1.matches(':popover-open'));
    153  },'Moving focus outside the popover should not dismiss the popover');
    154 
    155  promise_test(async () => {
    156    popover1.showPopover();
    157    popover2.showPopover();
    158    await waitForRender();
    159    p1HideCount = popover1HideCount;
    160    let p2HideCount = popover2HideCount;
    161    await clickOn(inside2);
    162    assert_true(popover1.matches(':popover-open'),'popover1 should be open');
    163    assert_true(popover2.matches(':popover-open'),'popover2 should be open');
    164    assert_equals(popover1HideCount,p1HideCount,'popover1');
    165    assert_equals(popover2HideCount,p2HideCount,'popover2');
    166    popover1.hidePopover();
    167    assert_false(popover1.matches(':popover-open'));
    168    assert_false(popover2.matches(':popover-open'));
    169  },'Clicking inside a child popover shouldn\'t close either popover');
    170 
    171  promise_test(async () => {
    172    popover1.showPopover();
    173    popover2.showPopover();
    174    await waitForRender();
    175    p1HideCount = popover1HideCount;
    176    p2HideCount = popover2HideCount;
    177    await clickOn(inside1);
    178    assert_true(popover1.matches(':popover-open'));
    179    assert_equals(popover1HideCount,p1HideCount);
    180    assert_false(popover2.matches(':popover-open'));
    181    assert_equals(popover2HideCount,p2HideCount+1);
    182    popover1.hidePopover();
    183  },'Clicking inside a parent popover should close child popover');
    184 
    185  promise_test(async () => {
    186    await clickOn(button1show);
    187    assert_true(popover1.matches(':popover-open'));
    188    await waitForRender();
    189    p1HideCount = popover1HideCount;
    190    await clickOn(button1show);
    191    assert_true(popover1.matches(':popover-open'),'popover1 should stay open');
    192    assert_equals(popover1HideCount,p1HideCount,'popover1 should not get hidden and reshown');
    193    popover1.hidePopover(); // Cleanup
    194    assert_false(popover1.matches(':popover-open'));
    195  },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover');
    196 
    197  promise_test(async () => {
    198    popover1.showPopover();
    199    assert_true(popover1.matches(':popover-open'));
    200    assert_false(popover2.matches(':popover-open'));
    201    await clickOn(button2);
    202    assert_true(popover2.matches(':popover-open'),'button2 should activate popover2');
    203    p2HideCount = popover2HideCount;
    204    await clickOn(button2);
    205    assert_true(popover2.matches(':popover-open'),'popover2 should stay open');
    206    assert_equals(popover2HideCount,p2HideCount,'popover2 should not get hidden and reshown');
    207    popover1.hidePopover(); // Cleanup
    208    assert_false(popover1.matches(':popover-open'));
    209    assert_false(popover2.matches(':popover-open'));
    210  },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover (nested case)');
    211 
    212  promise_test(async () => {
    213    popover1.showPopover();
    214    popover2.showPopover();
    215    assert_true(popover1.matches(':popover-open'));
    216    assert_true(popover2.matches(':popover-open'));
    217    p2HideCount = popover2HideCount;
    218    await clickOn(button2);
    219    assert_true(popover2.matches(':popover-open'),'popover2 should stay open');
    220    assert_equals(popover2HideCount,p2HideCount,'popover2 should not get hidden and reshown');
    221    popover1.hidePopover(); // Cleanup
    222    assert_false(popover1.matches(':popover-open'));
    223    assert_false(popover2.matches(':popover-open'));
    224  },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover (nested case, not used for invocation)');
    225 
    226  promise_test(async () => {
    227    popover1.showPopover(); // Directly show the popover
    228    assert_true(popover1.matches(':popover-open'));
    229    await waitForRender();
    230    p1HideCount = popover1HideCount;
    231    await clickOn(button1show);
    232    assert_true(popover1.matches(':popover-open'),'popover1 should stay open');
    233    assert_equals(popover1HideCount,p1HideCount,'popover1 should not get hidden and reshown');
    234    popover1.hidePopover(); // Cleanup
    235    assert_false(popover1.matches(':popover-open'));
    236  },'Clicking on invoking element, even if it wasn\'t used for activation, shouldn\'t close its popover');
    237 
    238  promise_test(async () => {
    239    popover1.showPopover(); // Directly show the popover
    240    assert_true(popover1.matches(':popover-open'));
    241    await waitForRender();
    242    p1HideCount = popover1HideCount;
    243    await clickOn(button1toggle);
    244    assert_false(popover1.matches(':popover-open'),'popover1 should be hidden by popovertarget');
    245    assert_equals(popover1HideCount,p1HideCount+1,'popover1 should get hidden only once by popovertarget');
    246  },'Clicking on popovertarget element, even if it wasn\'t used for activation, should hide it exactly once');
    247 
    248  promise_test(async () => {
    249    popover1.showPopover();
    250    popover2.showPopover(); // Popover1 is an ancestral element for popover2.
    251    assert_true(popover1.matches(':popover-open'));
    252    assert_true(popover2.matches(':popover-open'));
    253    const drag_actions = new test_driver.Actions();
    254    // Drag *from* popover2 *to* popover1 (its ancestor).
    255    await drag_actions.pointerMove(0,0,{origin: popover2})
    256      .pointerDown({button: drag_actions.ButtonType.LEFT})
    257      .pointerMove(0,0,{origin: popover1})
    258      .pointerUp({button: drag_actions.ButtonType.LEFT})
    259      .send();
    260    assert_true(popover1.matches(':popover-open'),'popover1 should be open');
    261    assert_true(popover2.matches(':popover-open'),'popover1 should be open');
    262    popover1.hidePopover();
    263    assert_false(popover2.matches(':popover-open'));
    264  },'Dragging from an open popover outside an open popover should leave the popover open');
    265 </script>
    266 
    267 <button id=b3 popovertarget=p3>Popover 3 - button 3
    268  <div popover id=p4>Inside popover 4</div>
    269 </button>
    270 <div popover id=p3>Inside popover 3</div>
    271 <div popover id=p5>Inside popover 5
    272  <button popovertarget=p3>Popover 3 - button 4 - unused</button>
    273 </div>
    274 <style>
    275  #p3 {top:100px;}
    276  #p4 {top:200px;}
    277  #p5 {top:200px;}
    278 </style>
    279 <script>
    280  const popover3 = document.querySelector('#p3');
    281  const popover4 = document.querySelector('#p4');
    282  const popover5 = document.querySelector('#p5');
    283  const button3 = document.querySelector('#b3');
    284  promise_test(async () => {
    285    await clickOn(button3);
    286    assert_true(popover3.matches(':popover-open'),'invoking element should open popover');
    287    popover4.showPopover();
    288    assert_true(popover4.matches(':popover-open'));
    289    assert_false(popover3.matches(':popover-open'),'popover3 is unrelated to popover4');
    290    popover4.hidePopover(); // Cleanup
    291    assert_false(popover4.matches(':popover-open'));
    292  },'A popover inside an invoking element doesn\'t participate in that invoker\'s ancestor chain');
    293 
    294  promise_test(async () => {
    295    popover5.showPopover();
    296    assert_true(popover5.matches(':popover-open'));
    297    assert_false(popover3.matches(':popover-open'));
    298    popover3.showPopover();
    299    assert_true(popover3.matches(':popover-open'));
    300    assert_false(popover5.matches(':popover-open'),'Popover 5 was not invoked from popover3\'s invoker');
    301    popover3.hidePopover();
    302    assert_false(popover3.matches(':popover-open'));
    303  },'An invoking element that was not used to invoke the popover is not part of the ancestor chain');
    304 </script>
    305 
    306 <my-element id="myElement">
    307  <template shadowrootmode="open">
    308    <button id=b7 popovertarget=p7 popovertargetaction=show tabindex="0">Popover7</button>
    309    <div popover id=p7 style="top: 100px;">
    310      <p>Popover content.</p>
    311      <input id="inside7" type="text" placeholder="some text">
    312    </div>
    313  </template>
    314 </my-element>
    315 <script>
    316  const button7 = document.querySelector('#myElement').shadowRoot.querySelector('#b7');
    317  const popover7 = document.querySelector('#myElement').shadowRoot.querySelector('#p7');
    318  const inside7 = document.querySelector('#myElement').shadowRoot.querySelector('#inside7');
    319  promise_test(async () => {
    320    button7.click();
    321    assert_true(popover7.matches(':popover-open'),'invoking element should open popover');
    322    inside7.click();
    323    assert_true(popover7.matches(':popover-open'));
    324    popover7.hidePopover();
    325  },'Clicking inside a shadow DOM popover does not close that popover');
    326 
    327  promise_test(async () => {
    328    button7.click();
    329    inside7.click();
    330    assert_true(popover7.matches(':popover-open'));
    331    await clickOn(outside);
    332    assert_false(popover7.matches(':popover-open'));
    333  },'Clicking outside a shadow DOM popover should close that popover');
    334 </script>
    335 
    336 <div popover id=p8>
    337  <button tabindex="0">Button</button>
    338  <span id=inside8after>Inside popover 8 after button</span>
    339 </div>
    340 <button id=p8invoker popovertarget=p8 tabindex="0">Popover8 invoker (no action)</button>
    341 <script>
    342  promise_test(async () => {
    343    const popover8 = document.querySelector('#p8');
    344    const inside8After = document.querySelector('#inside8after');
    345    const popover8Invoker = document.querySelector('#p8invoker');
    346    assert_false(popover8.matches(':popover-open'));
    347    popover8.showPopover();
    348    await clickOn(inside8After);
    349    assert_true(popover8.matches(':popover-open'));
    350    await sendTab();
    351    assert_equals(document.activeElement,popover8Invoker,'Focus should move to the invoker element');
    352    assert_true(popover8.matches(':popover-open'),'popover should stay open');
    353    popover8.hidePopover(); // Cleanup
    354  },'Moving focus back to the invoker element should not dismiss the popover');
    355 </script>
    356 
    357 <!-- Convoluted ancestor relationship -->
    358 <div popover id=convoluted_p1>Popover 1
    359  <button popovertarget=convoluted_p2>Open Popover 2</button>
    360 <div popover id=convoluted_p2>Popover 2
    361    <button popovertarget=convoluted_p3>Open Popover 3</button>
    362    <button popovertarget=convoluted_p2 popovertargetaction=show>Self-linked invoker</button>
    363  </div>
    364  <div popover id=convoluted_p3>Popover 3
    365    <button popovertarget=convoluted_p4>Open Popover 4</button>
    366  </div>
    367  <div popover id=convoluted_p4><p>Popover 4</p></div>
    368 </div>
    369 <button onclick="convoluted_p1.showPopover()" tabindex="0">Open convoluted popover</button>
    370 <style>
    371  #convoluted_p1 {top:50px;}
    372  #convoluted_p2 {top:150px;}
    373  #convoluted_p3 {top:250px;}
    374  #convoluted_p4 {top:350px;}
    375 </style>
    376 <script>
    377 const convPopover1 = document.querySelector('#convoluted_p1');
    378 const convPopover2 = document.querySelector('#convoluted_p2');
    379 const convPopover3 = document.querySelector('#convoluted_p3');
    380 const convPopover4 = document.querySelector('#convoluted_p4');
    381 promise_test(async () => {
    382  convPopover1.showPopover(); // Programmatically open p1
    383  assert_true(convPopover1.matches(':popover-open'));
    384  convPopover1.querySelector('button').click(); // Click to invoke p2
    385  assert_true(convPopover1.matches(':popover-open'));
    386  assert_true(convPopover2.matches(':popover-open'));
    387  convPopover2.querySelector('button').click(); // Click to invoke p3
    388  assert_true(convPopover1.matches(':popover-open'));
    389  assert_true(convPopover2.matches(':popover-open'));
    390  assert_true(convPopover3.matches(':popover-open'));
    391  convPopover3.querySelector('button').click(); // Click to invoke p4
    392  assert_true(convPopover1.matches(':popover-open'));
    393  assert_true(convPopover2.matches(':popover-open'));
    394  assert_true(convPopover3.matches(':popover-open'));
    395  assert_true(convPopover4.matches(':popover-open'));
    396  convPopover4.firstElementChild.click(); // Click within p4
    397  assert_true(convPopover1.matches(':popover-open'));
    398  assert_true(convPopover2.matches(':popover-open'));
    399  assert_true(convPopover3.matches(':popover-open'));
    400  assert_true(convPopover4.matches(':popover-open'));
    401  convPopover1.hidePopover();
    402  assert_false(convPopover1.matches(':popover-open'));
    403  assert_false(convPopover2.matches(':popover-open'));
    404  assert_false(convPopover3.matches(':popover-open'));
    405  assert_false(convPopover4.matches(':popover-open'));
    406 },'Ensure circular/convoluted ancestral relationships are functional');
    407 
    408 promise_test(async () => {
    409  convPopover1.showPopover(); // Programmatically open p1
    410  convPopover1.querySelector('button').click(); // Click to invoke p2
    411  assert_true(convPopover1.matches(':popover-open'));
    412  assert_true(convPopover2.matches(':popover-open'));
    413  assert_false(convPopover3.matches(':popover-open'));
    414  assert_false(convPopover4.matches(':popover-open'));
    415  convPopover4.showPopover(); // Programmatically open p4
    416  assert_true(convPopover1.matches(':popover-open'),'popover1 stays open because it is a DOM ancestor of popover4');
    417  assert_false(convPopover2.matches(':popover-open'),'popover2 closes because it isn\'t connected to popover4 via active invokers');
    418  assert_true(convPopover4.matches(':popover-open'));
    419  convPopover4.firstElementChild.click(); // Click within p4
    420  assert_true(convPopover1.matches(':popover-open'),'nothing changes');
    421  assert_false(convPopover2.matches(':popover-open'));
    422  assert_true(convPopover4.matches(':popover-open'));
    423  convPopover1.hidePopover();
    424  assert_false(convPopover1.matches(':popover-open'));
    425  assert_false(convPopover2.matches(':popover-open'));
    426  assert_false(convPopover3.matches(':popover-open'));
    427  assert_false(convPopover4.matches(':popover-open'));
    428 },'Ensure circular/convoluted ancestral relationships are functional, with a direct showPopover()');
    429 </script>
    430 
    431 <div popover id=p13>Popover 1
    432  <div popover id=p14>Popover 2
    433    <div popover id=p15>Popover 3</div>
    434  </div>
    435 </div>
    436 <style>
    437  #p13 {top: 100px;}
    438  #p14 {top: 200px;}
    439  #p15 {top: 300px;}
    440 </style>
    441 <script>
    442 promise_test(async () => {
    443  const p13 = document.querySelector('#p13');
    444  const p14 = document.querySelector('#p14');
    445  const p15 = document.querySelector('#p15');
    446  p13.showPopover();
    447  p14.showPopover();
    448  p15.showPopover();
    449  p15.addEventListener('beforetoggle', (e) => {
    450    if (e.newState !== "closed")
    451      return;
    452    p14.hidePopover();
    453  },{once:true});
    454  assert_true(p13.matches(':popover-open') && p14.matches(':popover-open') && p15.matches(':popover-open'),'all three should be open');
    455  p14.hidePopover();
    456  assert_true(p13.matches(':popover-open'),'p13 should still be open');
    457  assert_false(p14.matches(':popover-open'));
    458  assert_false(p15.matches(':popover-open'));
    459  p13.hidePopover(); // Cleanup
    460 },'Hide the target popover during "hide all popovers until"');
    461 </script>
    462 
    463 <div id=p16 popover>Popover 16
    464    <div id=p17 popover>Popover 17</div>
    465    <div id=p18 popover>Popover 18</div>
    466 </div>
    467 
    468 <script>
    469 promise_test(async () => {
    470  p16.showPopover();
    471  p18.showPopover();
    472  let events = [];
    473  const logEvents = (e) => {events.push(`${e.newState==='open' ? 'show' : 'hide'} ${e.target.id}`)};
    474  p16.addEventListener('beforetoggle', logEvents);
    475  p17.addEventListener('beforetoggle', logEvents);
    476  p18.addEventListener('beforetoggle', (e) => {
    477    logEvents(e);
    478    p17.showPopover();
    479  });
    480  p16.hidePopover();
    481  assert_array_equals(events,['hide p18','show p17','hide p16'],'There should not be a hide event for p17');
    482  assert_false(p16.matches(':popover-open'));
    483  assert_false(p17.matches(':popover-open'));
    484  assert_false(p18.matches(':popover-open'));
    485 },'Show a sibling popover during "hide all popovers until"');
    486 </script>
    487 
    488 <div id=p19 popover>Popover 19</div>
    489 <div id=p20 popover>Popover 20</div>
    490 <button id=example2 tabindex="0">Example 2</button>
    491 
    492 <script>
    493 promise_test(async () => {
    494  p19.showPopover();
    495  let events = [];
    496  const logEvents = (e) => {events.push(`${e.newState==='open' ? 'show' : 'hide'} ${e.target.id}`)};
    497  p19.addEventListener('beforetoggle', (e) => {
    498    logEvents(e);
    499    p20.showPopover();
    500  });
    501  p20.addEventListener('beforetoggle', logEvents);
    502  p19.hidePopover();
    503  assert_array_equals(events,['hide p19','show p20'],'There should not be a second hide event for 19');
    504  assert_false(p19.matches(':popover-open'));
    505  assert_true(p20.matches(':popover-open'));
    506  p20.hidePopover(); // Cleanup
    507 },'Show an unrelated popover during "hide popover"');
    508 </script>
    509 
    510 <div id=p21 popover>21
    511  <div id=p22 popover>22</div>
    512  <div id=p23 popover>23</div>
    513  <div id=p24 popover>24</div>
    514 </div>
    515 
    516 <script>
    517 promise_test(async () => {
    518  p21.showPopover();
    519  p22.showPopover();
    520  let events = [];
    521  const logEvents = (e) => { events.push(`${e.newState === 'open' ? 'show' : 'hide'} ${e.target.id}`) };
    522  p22.addEventListener('beforetoggle', (e) => {
    523    logEvents(e);
    524    p24.showPopover()
    525  });
    526  p23.addEventListener('beforetoggle', logEvents);
    527  p24.addEventListener('beforetoggle', logEvents);
    528  p23.showPopover();
    529  assert_array_equals(events, ['show p23', 'hide p22', 'show p24'], 'hiding p24 does not fire event');
    530  assert_false(p22.matches(':popover-open'));
    531  assert_true(p23.matches(':popover-open'));
    532  assert_false(p24.matches(':popover-open'));
    533  p21.hidePopover(); // Cleanup
    534 },'Show other auto popover during "hide all popover until"');
    535 </script>
    536 
    537 <div id=p25 popover>
    538  <div id=p26 popover>26</div>
    539  <div id=p27 popover>27</div>
    540  <div id=p28 popover>28</div>
    541 </div>
    542 <script>
    543 promise_test(async () => {
    544  p25.showPopover();
    545  p26.showPopover();
    546  let events = [];
    547  const logEvents = (e) => { events.push(`${e.newState === 'open' ? 'show' : 'hide'} ${e.target.id}`) };
    548  p26.addEventListener('beforetoggle', (e) => {
    549    logEvents(e);
    550    p28.showPopover();
    551  });
    552  p27.addEventListener('beforetoggle', logEvents);
    553  p28.addEventListener('beforetoggle', (e) => {
    554    logEvents(e);
    555    p27.showPopover();
    556  });
    557  p27.showPopover();
    558  assert_array_equals(events, ['show p27', 'hide p26', 'show p28', 'show p27'], 'Nested showPopover should not fire event for its HideAllPopoversUntil');
    559  assert_false(p26.matches(':popover-open'));
    560  assert_true(p27.matches(':popover-open'));
    561  assert_false(p28.matches(':popover-open'));
    562  p25.hidePopover(); // Cleanup
    563 }, 'Nested showPopover');
    564 </script>
    565 
    566 <div id=p29 popover>Popover 29</div>
    567 <button id=b29 popovertarget=p29>Open popover 29</button>
    568 <iframe id=iframe29 width=100 height=30></iframe>
    569 <script>
    570 promise_test(async () => {
    571  let iframe_url = (new URL("/common/blank.html", location.href)).href;
    572  iframe29.src = iframe_url;
    573  iframe29.contentDocument.body.style.height = '100%';
    574  assert_false(p29.matches(':popover-open'),'initially hidden');
    575  p29.showPopover();
    576  assert_true(p29.matches(':popover-open'),'showing');
    577  let actions = new test_driver.Actions();
    578  // Using the iframe's contentDocument as the origin would throw an error, so
    579  // we are using iframe29 as the origin instead.
    580  const iframe_box = iframe29.getBoundingClientRect();
    581 
    582  await actions
    583      .pointerMove(1,1,{origin: b29})
    584      .pointerDown({button: actions.ButtonType.LEFT})
    585      .pointerMove(iframe_box.width / 2, iframe_box.height / 2, {origin: iframe29})
    586      .pointerUp({button: actions.ButtonType.LEFT})
    587      .send();
    588  assert_true(p29.matches(':popover-open'), 'popover should be open after pointerUp in iframe.');
    589 
    590  actions = new test_driver.Actions();
    591  await actions
    592      .pointerMove(iframe_box.width / 2, iframe_box.height / 2, {origin: iframe29})
    593      .pointerDown({button: actions.ButtonType.LEFT})
    594      .pointerMove(1,1,{origin: b29})
    595      .pointerUp({button: actions.ButtonType.LEFT})
    596      .send();
    597  assert_true(p29.matches(':popover-open'), 'popover should be open after pointerUp on main frame button.');
    598 },`Pointer down in one document and pointer up in another document shouldn't dismiss popover`);
    599 </script>
    600 
    601 <div id=p30 popover>Popover 30</div>
    602 <button id=b30 popovertarget=p30>Open popover 30</button>
    603 <button id=b30b>Non-invoker</button>
    604 <script>
    605 promise_test(async () => {
    606  assert_false(p30.matches(':popover-open'),'initially hidden');
    607  p30.showPopover();
    608  assert_true(p30.matches(':popover-open'),'showing');
    609  let actions = new test_driver.Actions();
    610  await actions
    611      .pointerMove(2,2,{origin: b30})
    612      .pointerDown({button: actions.ButtonType.LEFT})
    613      .pointerMove(2,2,{origin: b30b})
    614      .pointerUp({button: actions.ButtonType.LEFT})
    615      .send();
    616  await waitForRender();
    617  assert_true(p30.matches(':popover-open'),'showing after pointerup');
    618 },`Pointer down inside invoker and up outside that invoker shouldn't dismiss popover`);
    619 </script>