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