| <!DOCTYPE html> |
| <meta charset="utf-8" /> |
| <title>Popover light dismiss behavior</title> |
| <meta name="timeout" content="long"> |
| <link rel="author" href="mailto:masonf@chromium.org"> |
| <link rel=help href="https://open-ui.org/components/popover.research.explainer"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/resources/testdriver.js"></script> |
| <script src="/resources/testdriver-actions.js"></script> |
| <script src="/resources/testdriver-vendor.js"></script> |
| <script src="resources/popover-utils.js"></script> |
| |
| <button id=b1t popovertarget='p1'>Popover 1</button> |
| <button id=b1s popovertarget='p1' popovertargetaction=show>Popover 1</button> |
| <button id=p1anchor tabindex="0">Popover1 anchor (no action)</button> |
| <span id=outside>Outside all popovers</span> |
| <div popover id=p1 anchor=p1anchor> |
| <span id=inside1>Inside popover 1</span> |
| <button id=b2 popovertarget='p2' popovertargetaction=show>Popover 2</button> |
| <span id=inside1after>Inside popover 1 after button</span> |
| </div> |
| <div popover id=p2 anchor=b2> |
| <span id=inside2>Inside popover 2</span> |
| </div> |
| <button id=after_p1 tabindex="0">Next control after popover1</button> |
| <style> |
| #p1 {top: 50px;} |
| #p2 {top: 120px;} |
| [popover] {bottom:auto;} |
| [popover]::backdrop { |
| /* This should *not* affect anything: */ |
| pointer-events: auto; |
| } |
| </style> |
| <script> |
| const popover1 = document.querySelector('#p1'); |
| const button1toggle = document.querySelector('#b1t'); |
| const button1show = document.querySelector('#b1s'); |
| const popover1anchor = document.querySelector('#p1anchor'); |
| const inside1After = document.querySelector('#inside1after'); |
| const button2 = document.querySelector('#b2'); |
| const popover2 = document.querySelector('#p2'); |
| const outside = document.querySelector('#outside'); |
| const inside1 = document.querySelector('#inside1'); |
| const inside2 = document.querySelector('#inside2'); |
| const afterp1 = document.querySelector('#after_p1'); |
| |
| let popover1HideCount = 0; |
| popover1.addEventListener('beforetoggle',(e) => { |
| if (e.newState !== "closed") |
| return; |
| ++popover1HideCount; |
| e.preventDefault(); // 'beforetoggle' should not be cancellable. |
| }); |
| let popover2HideCount = 0; |
| popover2.addEventListener('beforetoggle',(e) => { |
| if (e.newState !== "closed") |
| return; |
| ++popover2HideCount; |
| e.preventDefault(); // 'beforetoggle' should not be cancellable. |
| }); |
| promise_test(async () => { |
| assert_false(popover1.matches(':popover-open')); |
| popover1.showPopover(); |
| assert_true(popover1.matches(':popover-open')); |
| let p1HideCount = popover1HideCount; |
| await clickOn(outside); |
| assert_false(popover1.matches(':popover-open')); |
| assert_equals(popover1HideCount,p1HideCount+1); |
| },'Clicking outside a popover will dismiss the popover'); |
| |
| promise_test(async (t) => { |
| const controller = new AbortController(); |
| t.add_cleanup(() => controller.abort()); |
| function addListener(eventName) { |
| document.addEventListener(eventName,(e) => e.preventDefault(),{signal:controller.signal,capture: true}); |
| } |
| addListener('pointerdown'); |
| addListener('pointerup'); |
| addListener('mousedown'); |
| addListener('mouseup'); |
| assert_false(popover1.matches(':popover-open')); |
| popover1.showPopover(); |
| assert_true(popover1.matches(':popover-open')); |
| let p1HideCount = popover1HideCount; |
| await clickOn(outside); |
| assert_false(popover1.matches(':popover-open'),'preventDefault should not prevent light dismiss'); |
| assert_equals(popover1HideCount,p1HideCount+1); |
| },'Canceling pointer events should not keep clicks from light dismissing popovers'); |
| |
| promise_test(async () => { |
| assert_false(popover1.matches(':popover-open')); |
| popover1.showPopover(); |
| await waitForRender(); |
| p1HideCount = popover1HideCount; |
| await clickOn(inside1); |
| assert_true(popover1.matches(':popover-open')); |
| assert_equals(popover1HideCount,p1HideCount); |
| popover1.hidePopover(); |
| },'Clicking inside a popover does not close that popover'); |
| |
| promise_test(async () => { |
| assert_false(popover1.matches(':popover-open')); |
| popover1.showPopover(); |
| await waitForRender(); |
| assert_true(popover1.matches(':popover-open')); |
| const actions = new test_driver.Actions(); |
| await actions.pointerMove(0, 0, {origin: outside}) |
| .pointerDown({button: actions.ButtonType.LEFT}) |
| .send(); |
| await waitForRender(); |
| assert_true(popover1.matches(':popover-open'),'pointerdown (outside the popover) should not hide the popover'); |
| await actions.pointerUp({button: actions.ButtonType.LEFT}) |
| .send(); |
| await waitForRender(); |
| assert_false(popover1.matches(':popover-open'),'pointerup (outside the popover) should trigger light dismiss'); |
| },'Popovers close on pointerup, not pointerdown'); |
| |
| promise_test(async (t) => { |
| t.add_cleanup(() => popover1.hidePopover()); |
| assert_false(popover1.matches(':popover-open')); |
| popover1.showPopover(); |
| assert_true(popover1.matches(':popover-open')); |
| async function testOne(eventName) { |
| document.body.dispatchEvent(new PointerEvent(eventName)); |
| document.body.dispatchEvent(new MouseEvent(eventName)); |
| document.body.dispatchEvent(new ProgressEvent(eventName)); |
| await waitForRender(); |
| assert_true(popover1.matches(':popover-open'),`A synthetic "${eventName}" event should not hide the popover`); |
| } |
| await testOne('pointerup'); |
| await testOne('pointerdown'); |
| await testOne('mouseup'); |
| await testOne('mousedown'); |
| },'Synthetic events can\'t close popovers'); |
| |
| promise_test(async (t) => { |
| t.add_cleanup(() => popover1.hidePopover()); |
| popover1.showPopover(); |
| await clickOn(inside1After); |
| assert_true(popover1.matches(':popover-open')); |
| await sendTab(); |
| assert_equals(document.activeElement,afterp1,'Focus should move to a button outside the popover'); |
| assert_true(popover1.matches(':popover-open')); |
| },'Moving focus outside the popover should not dismiss the popover'); |
| |
| promise_test(async () => { |
| popover1.showPopover(); |
| popover2.showPopover(); |
| await waitForRender(); |
| p1HideCount = popover1HideCount; |
| let p2HideCount = popover2HideCount; |
| await clickOn(inside2); |
| assert_true(popover1.matches(':popover-open'),'popover1 should be open'); |
| assert_true(popover2.matches(':popover-open'),'popover2 should be open'); |
| assert_equals(popover1HideCount,p1HideCount,'popover1'); |
| assert_equals(popover2HideCount,p2HideCount,'popover2'); |
| popover1.hidePopover(); |
| assert_false(popover1.matches(':popover-open')); |
| assert_false(popover2.matches(':popover-open')); |
| },'Clicking inside a child popover shouldn\'t close either popover'); |
| |
| promise_test(async () => { |
| popover1.showPopover(); |
| popover2.showPopover(); |
| await waitForRender(); |
| p1HideCount = popover1HideCount; |
| p2HideCount = popover2HideCount; |
| await clickOn(inside1); |
| assert_true(popover1.matches(':popover-open')); |
| assert_equals(popover1HideCount,p1HideCount); |
| assert_false(popover2.matches(':popover-open')); |
| assert_equals(popover2HideCount,p2HideCount+1); |
| popover1.hidePopover(); |
| },'Clicking inside a parent popover should close child popover'); |
| |
| promise_test(async () => { |
| await clickOn(button1show); |
| assert_true(popover1.matches(':popover-open')); |
| await waitForRender(); |
| p1HideCount = popover1HideCount; |
| await clickOn(button1show); |
| assert_true(popover1.matches(':popover-open'),'popover1 should stay open'); |
| assert_equals(popover1HideCount,p1HideCount,'popover1 should not get hidden and reshown'); |
| popover1.hidePopover(); // Cleanup |
| assert_false(popover1.matches(':popover-open')); |
| },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover'); |
| |
| promise_test(async () => { |
| popover1.showPopover(); |
| assert_true(popover1.matches(':popover-open')); |
| assert_false(popover2.matches(':popover-open')); |
| await clickOn(button2); |
| assert_true(popover2.matches(':popover-open'),'button2 should activate popover2'); |
| p2HideCount = popover2HideCount; |
| await clickOn(button2); |
| assert_true(popover2.matches(':popover-open'),'popover2 should stay open'); |
| assert_equals(popover2HideCount,p2HideCount,'popover2 should not get hidden and reshown'); |
| popover1.hidePopover(); // Cleanup |
| assert_false(popover1.matches(':popover-open')); |
| assert_false(popover2.matches(':popover-open')); |
| },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover (nested case)'); |
| |
| promise_test(async () => { |
| popover1.showPopover(); |
| popover2.showPopover(); |
| assert_true(popover1.matches(':popover-open')); |
| assert_true(popover2.matches(':popover-open')); |
| p2HideCount = popover2HideCount; |
| await clickOn(button2); |
| assert_true(popover2.matches(':popover-open'),'popover2 should stay open'); |
| assert_equals(popover2HideCount,p2HideCount,'popover2 should not get hidden and reshown'); |
| popover1.hidePopover(); // Cleanup |
| assert_false(popover1.matches(':popover-open')); |
| assert_false(popover2.matches(':popover-open')); |
| },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover (nested case, not used for invocation)'); |
| |
| promise_test(async () => { |
| popover1.showPopover(); // Directly show the popover |
| assert_true(popover1.matches(':popover-open')); |
| await waitForRender(); |
| p1HideCount = popover1HideCount; |
| await clickOn(button1show); |
| assert_true(popover1.matches(':popover-open'),'popover1 should stay open'); |
| assert_equals(popover1HideCount,p1HideCount,'popover1 should not get hidden and reshown'); |
| popover1.hidePopover(); // Cleanup |
| assert_false(popover1.matches(':popover-open')); |
| },'Clicking on invoking element, even if it wasn\'t used for activation, shouldn\'t close its popover'); |
| |
| promise_test(async () => { |
| popover1.showPopover(); // Directly show the popover |
| assert_true(popover1.matches(':popover-open')); |
| await waitForRender(); |
| p1HideCount = popover1HideCount; |
| await clickOn(button1toggle); |
| assert_false(popover1.matches(':popover-open'),'popover1 should be hidden by popovertarget'); |
| assert_equals(popover1HideCount,p1HideCount+1,'popover1 should get hidden only once by popovertarget'); |
| },'Clicking on popovertarget element, even if it wasn\'t used for activation, should hide it exactly once'); |
| |
| promise_test(async () => { |
| popover1.showPopover(); |
| assert_true(popover1.matches(':popover-open')); |
| await waitForRender(); |
| await clickOn(popover1anchor); |
| assert_false(popover1.matches(':popover-open'),'popover1 should close'); |
| },'Clicking on anchor element (that isn\'t an invoking element) shouldn\'t prevent its popover from being closed'); |
| |
| promise_test(async () => { |
| popover1.showPopover(); |
| popover2.showPopover(); // Popover1 is an ancestral element for popover2. |
| assert_true(popover1.matches(':popover-open')); |
| assert_true(popover2.matches(':popover-open')); |
| const drag_actions = new test_driver.Actions(); |
| // Drag *from* popover2 *to* popover1 (its ancestor). |
| await drag_actions.pointerMove(0,0,{origin: popover2}) |
| .pointerDown({button: drag_actions.ButtonType.LEFT}) |
| .pointerMove(0,0,{origin: popover1}) |
| .pointerUp({button: drag_actions.ButtonType.LEFT}) |
| .send(); |
| assert_true(popover1.matches(':popover-open'),'popover1 should be open'); |
| assert_true(popover2.matches(':popover-open'),'popover1 should be open'); |
| popover1.hidePopover(); |
| assert_false(popover2.matches(':popover-open')); |
| },'Dragging from an open popover outside an open popover should leave the popover open'); |
| </script> |
| |
| <button id=b3 popovertarget=p3>Popover 3 - button 3 |
| <div popover id=p4>Inside popover 4</div> |
| </button> |
| <div popover id=p3>Inside popover 3</div> |
| <div popover id=p5>Inside popover 5 |
| <button popovertarget=p3>Popover 3 - button 4 - unused</button> |
| </div> |
| <style> |
| #p3 {top:100px;} |
| #p4 {top:200px;} |
| #p5 {top:200px;} |
| </style> |
| <script> |
| const popover3 = document.querySelector('#p3'); |
| const popover4 = document.querySelector('#p4'); |
| const popover5 = document.querySelector('#p5'); |
| const button3 = document.querySelector('#b3'); |
| promise_test(async () => { |
| await clickOn(button3); |
| assert_true(popover3.matches(':popover-open'),'invoking element should open popover'); |
| popover4.showPopover(); |
| assert_true(popover4.matches(':popover-open')); |
| assert_false(popover3.matches(':popover-open'),'popover3 is unrelated to popover4'); |
| popover4.hidePopover(); // Cleanup |
| assert_false(popover4.matches(':popover-open')); |
| },'A popover inside an invoking element doesn\'t participate in that invoker\'s ancestor chain'); |
| |
| promise_test(async () => { |
| popover5.showPopover(); |
| assert_true(popover5.matches(':popover-open')); |
| assert_false(popover3.matches(':popover-open')); |
| popover3.showPopover(); |
| assert_true(popover3.matches(':popover-open')); |
| assert_false(popover5.matches(':popover-open'),'Popover 5 was not invoked from popover3\'s invoker'); |
| popover3.hidePopover(); |
| assert_false(popover3.matches(':popover-open')); |
| },'An invoking element that was not used to invoke the popover is not part of the ancestor chain'); |
| </script> |
| |
| <div popover id=p6>Inside popover 6 |
| <div style="height:2000px;background:lightgreen"></div> |
| Bottom of popover6 |
| </div> |
| <button popovertarget=p6>Popover 6</button> |
| <style> |
| #p6 { |
| width: 300px; |
| height: 300px; |
| overflow-y: scroll; |
| } |
| </style> |
| <script> |
| const popover6 = document.querySelector('#p6'); |
| promise_test(async () => { |
| popover6.showPopover(); |
| assert_equals(popover6.scrollTop,0,'popover6 should start non-scrolled'); |
| await new test_driver.Actions() |
| .scroll(0, 0, 0, 50, {origin: popover6}) |
| .send(); |
| assert_true(popover6.matches(':popover-open'),'popover6 should stay open'); |
| assert_equals(popover6.scrollTop,50,'popover6 should be scrolled'); |
| popover6.hidePopover(); |
| },'Scrolling within a popover should not close the popover'); |
| </script> |
| |
| <my-element id="myElement"> |
| <template shadowrootmode="open"> |
| <button id=b7 onclick='showPopover7()' tabindex="0">Popover7</button> |
| <div popover id=p7 anchor=b7 style="top: 100px;"> |
| <p>Popover content.</p> |
| <input id="inside7" type="text" placeholder="some text"> |
| </div> |
| </template> |
| </my-element> |
| <script> |
| const button7 = document.querySelector('#myElement').shadowRoot.querySelector('#b7'); |
| const popover7 = document.querySelector('#myElement').shadowRoot.querySelector('#p7'); |
| const inside7 = document.querySelector('#myElement').shadowRoot.querySelector('#inside7'); |
| function showPopover7() { |
| popover7.showPopover(); |
| } |
| promise_test(async () => { |
| button7.click(); |
| assert_true(popover7.matches(':popover-open'),'invoking element should open popover'); |
| inside7.click(); |
| assert_true(popover7.matches(':popover-open')); |
| popover7.hidePopover(); |
| },'Clicking inside a shadow DOM popover does not close that popover'); |
| |
| promise_test(async () => { |
| button7.click(); |
| inside7.click(); |
| assert_true(popover7.matches(':popover-open')); |
| await clickOn(outside); |
| assert_false(popover7.matches(':popover-open')); |
| },'Clicking outside a shadow DOM popover should close that popover'); |
| </script> |
| |
| <div popover id=p8 anchor=p8anchor> |
| <button tabindex="0">Button</button> |
| <span id=inside8after>Inside popover 8 after button</span> |
| </div> |
| <button id=p8anchor tabindex="0">Popover8 anchor (no action)</button> |
| <script> |
| promise_test(async () => { |
| const popover8 = document.querySelector('#p8'); |
| const inside8After = document.querySelector('#inside8after'); |
| const popover8Anchor = document.querySelector('#p8anchor'); |
| assert_false(popover8.matches(':popover-open')); |
| popover8.showPopover(); |
| await clickOn(inside8After); |
| assert_true(popover8.matches(':popover-open')); |
| await sendTab(); |
| assert_equals(document.activeElement,popover8Anchor,'Focus should move to the anchor element'); |
| assert_true(popover8.matches(':popover-open'),'popover should stay open'); |
| popover8.hidePopover(); // Cleanup |
| },'Moving focus back to the anchor element should not dismiss the popover'); |
| </script> |
| |
| <!-- Convoluted ancestor relationship --> |
| <div popover id=convoluted_p1>Popover 1 |
| <div id=convoluted_anchor>Anchor |
| <button popovertarget=convoluted_p2>Open Popover 2</button> |
| <div popover id=convoluted_p4><p>Popover 4</p></div> |
| </div> |
| </div> |
| <div popover id=convoluted_p2 anchor=convoluted_p2>Popover 2 (self-anchor-linked) |
| <button popovertarget=convoluted_p3>Open Popover 3</button> |
| <button popovertarget=convoluted_p2 popovertargetaction=show>Self-linked invoker</button> |
| </div> |
| <div popover id=convoluted_p3 anchor=convoluted_anchor>Popover 3 |
| <button popovertarget=convoluted_p4>Open Popover 4</button> |
| </div> |
| <button onclick="convoluted_p1.showPopover()" tabindex="0">Open convoluted popover</button> |
| <style> |
| #convoluted_p1 {top:50px;} |
| #convoluted_p2 {top:150px;} |
| #convoluted_p3 {top:250px;} |
| #convoluted_p4 {top:350px;} |
| </style> |
| <script> |
| const convPopover1 = document.querySelector('#convoluted_p1'); |
| const convPopover2 = document.querySelector('#convoluted_p2'); |
| const convPopover3 = document.querySelector('#convoluted_p3'); |
| const convPopover4 = document.querySelector('#convoluted_p4'); |
| promise_test(async () => { |
| convPopover1.showPopover(); // Programmatically open p1 |
| assert_true(convPopover1.matches(':popover-open')); |
| convPopover1.querySelector('button').click(); // Click to invoke p2 |
| assert_true(convPopover1.matches(':popover-open')); |
| assert_true(convPopover2.matches(':popover-open')); |
| convPopover2.querySelector('button').click(); // Click to invoke p3 |
| assert_true(convPopover1.matches(':popover-open')); |
| assert_true(convPopover2.matches(':popover-open')); |
| assert_true(convPopover3.matches(':popover-open')); |
| convPopover3.querySelector('button').click(); // Click to invoke p4 |
| assert_true(convPopover1.matches(':popover-open')); |
| assert_true(convPopover2.matches(':popover-open')); |
| assert_true(convPopover3.matches(':popover-open')); |
| assert_true(convPopover4.matches(':popover-open')); |
| convPopover4.firstElementChild.click(); // Click within p4 |
| assert_true(convPopover1.matches(':popover-open')); |
| assert_true(convPopover2.matches(':popover-open')); |
| assert_true(convPopover3.matches(':popover-open')); |
| assert_true(convPopover4.matches(':popover-open')); |
| convPopover1.hidePopover(); |
| assert_false(convPopover1.matches(':popover-open')); |
| assert_false(convPopover2.matches(':popover-open')); |
| assert_false(convPopover3.matches(':popover-open')); |
| assert_false(convPopover4.matches(':popover-open')); |
| },'Ensure circular/convoluted ancestral relationships are functional'); |
| |
| promise_test(async () => { |
| convPopover1.showPopover(); // Programmatically open p1 |
| convPopover1.querySelector('button').click(); // Click to invoke p2 |
| assert_true(convPopover1.matches(':popover-open')); |
| assert_true(convPopover2.matches(':popover-open')); |
| assert_false(convPopover3.matches(':popover-open')); |
| assert_false(convPopover4.matches(':popover-open')); |
| convPopover4.showPopover(); // Programmatically open p4 |
| assert_true(convPopover1.matches(':popover-open'),'popover1 stays open because it is a DOM ancestor of popover4'); |
| assert_false(convPopover2.matches(':popover-open'),'popover2 closes because it isn\'t connected to popover4 via active invokers'); |
| assert_true(convPopover4.matches(':popover-open')); |
| convPopover4.firstElementChild.click(); // Click within p4 |
| assert_true(convPopover1.matches(':popover-open'),'nothing changes'); |
| assert_false(convPopover2.matches(':popover-open')); |
| assert_true(convPopover4.matches(':popover-open')); |
| convPopover1.hidePopover(); |
| assert_false(convPopover1.matches(':popover-open')); |
| assert_false(convPopover2.matches(':popover-open')); |
| assert_false(convPopover3.matches(':popover-open')); |
| assert_false(convPopover4.matches(':popover-open')); |
| },'Ensure circular/convoluted ancestral relationships are functional, with a direct showPopover()'); |
| </script> |
| |
| <div popover id=p10>Popover</div> |
| <div popover=hint id=p11>Hint</div> |
| <div popover=manual id=p12>Manual</div> |
| <style> |
| #p10 {top:100px;} |
| #p11 {top:200px;} |
| #p12 {top:300px;} |
| </style> |
| <script> |
| if (popoverHintSupported()) { |
| promise_test(async () => { |
| const auto = document.querySelector('#p10'); |
| const hint = document.querySelector('#p11'); |
| const manual = document.querySelector('#p12'); |
| // All three can be open at once, if shown in this order: |
| auto.showPopover(); |
| hint.showPopover(); |
| manual.showPopover(); |
| assert_true(auto.matches(':popover-open')); |
| assert_true(hint.matches(':popover-open')); |
| assert_true(manual.matches(':popover-open')); |
| // Clicking the hint will close the auto, but not the manual. |
| await clickOn(hint); |
| assert_false(auto.matches(':popover-open'),'auto should be hidden'); |
| assert_true(hint.matches(':popover-open'),'hint should stay open'); |
| assert_true(manual.matches(':popover-open'),'manual does not light dismiss'); |
| // Clicking outside should close the hint, but not the manual: |
| await clickOn(outside); |
| assert_false(auto.matches(':popover-open')); |
| assert_false(hint.matches(':popover-open'),'hint should close'); |
| assert_true(manual.matches(':popover-open'),'manual does not light dismiss'); |
| manual.hidePopover(); |
| assert_false(manual.matches(':popover-open')); |
| auto.showPopover(); |
| hint.showPopover(); |
| assert_true(auto.matches(':popover-open')); |
| assert_true(hint.matches(':popover-open')); |
| // Clicking on the auto should close the hint: |
| await clickOn(auto); |
| assert_true(auto.matches(':popover-open'),'auto should stay open'); |
| assert_false(hint.matches(':popover-open'),'hint should light dismiss'); |
| auto.hidePopover(); |
| assert_false(auto.matches(':popover-open')); |
| },'Light dismiss of mixed popover types including hints'); |
| } |
| </script> |
| <div popover id=p13>Popover 1 |
| <div popover id=p14>Popover 2 |
| <div popover id=p15>Popover 3</div> |
| </div> |
| </div> |
| <style> |
| #p13 {top: 100px;} |
| #p14 {top: 200px;} |
| #p15 {top: 300px;} |
| </style> |
| <script> |
| promise_test(async () => { |
| const p13 = document.querySelector('#p13'); |
| const p14 = document.querySelector('#p14'); |
| const p15 = document.querySelector('#p15'); |
| p13.showPopover(); |
| p14.showPopover(); |
| p15.showPopover(); |
| p15.addEventListener('beforetoggle', (e) => { |
| if (e.newState !== "closed") |
| return; |
| p14.hidePopover(); |
| },{once:true}); |
| assert_true(p13.matches(':popover-open') && p14.matches(':popover-open') && p15.matches(':popover-open'),'all three should be open'); |
| assert_throws_dom('InvalidStateError',() => p14.hidePopover(),'should throw because the event listener has already hidden the popover'); |
| assert_true(p13.matches(':popover-open'),'p13 should still be open'); |
| assert_false(p14.matches(':popover-open')); |
| assert_false(p15.matches(':popover-open')); |
| p13.hidePopover(); // Cleanup |
| },'Hide the target popover during "hide all popovers until"'); |
| </script> |
| |
| <div id=p16 popover>Popover 16 |
| <div id=p17 popover>Popover 17</div> |
| <div id=p18 popover>Popover 18</div> |
| </div> |
| |
| <script> |
| promise_test(async () => { |
| p16.showPopover(); |
| p18.showPopover(); |
| let events = []; |
| const logEvents = (e) => {events.push(`${e.newState==='open' ? 'show' : 'hide'} ${e.target.id}`)}; |
| p16.addEventListener('beforetoggle', logEvents); |
| p17.addEventListener('beforetoggle', logEvents); |
| p18.addEventListener('beforetoggle', (e) => { |
| logEvents(e); |
| p17.showPopover(); |
| }); |
| p16.hidePopover(); |
| assert_array_equals(events,['hide p18','show p17','hide p16'],'There should not be a hide event for p17'); |
| assert_false(p16.matches(':popover-open')); |
| assert_false(p17.matches(':popover-open')); |
| assert_false(p18.matches(':popover-open')); |
| },'Show a sibling popover during "hide all popovers until"'); |
| </script> |
| |
| <div id=p19 popover>Popover 19</div> |
| <div id=p20 popover>Popover 20</div> |
| <button id=example2 tabindex="0">Example 2</button> |
| |
| <script> |
| promise_test(async () => { |
| p19.showPopover(); |
| let events = []; |
| const logEvents = (e) => {events.push(`${e.newState==='open' ? 'show' : 'hide'} ${e.target.id}`)}; |
| p19.addEventListener('beforetoggle', (e) => { |
| logEvents(e); |
| p20.showPopover(); |
| }); |
| p20.addEventListener('beforetoggle', logEvents); |
| // Because the `beforetoggle` handler shows a different popover, |
| // and that action closes the p19 popover, the call to hidePopover() |
| // will result in an exception. |
| assert_throws_dom('InvalidStateError',() => p19.hidePopover()); |
| assert_array_equals(events,['hide p19','show p20'],'There should not be a second hide event for 19'); |
| assert_false(p19.matches(':popover-open')); |
| assert_true(p20.matches(':popover-open')); |
| p20.hidePopover(); // Cleanup |
| },'Show an unrelated popover during "hide popover"'); |
| </script> |