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>
24 <div popover id=p2 anchor=b2
>
25 <span id=inside2
>Inside popover
2</span>
27 <button id=after_p1
tabindex=
"0">Next control after popover1
</button>
31 [popover] {bottom:auto;}
33 /* This should *not* affect anything: */
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")
55 e
.preventDefault(); // 'beforetoggle' should not be cancellable.
57 let popover2HideCount
= 0;
58 popover2
.addEventListener('beforetoggle',(e
) => {
59 if (e
.newState
!== "closed")
62 e
.preventDefault(); // 'beforetoggle' should not be cancellable.
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
})
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
})
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'));
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
})
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');
269 <button id=b3 popovertarget=p3
>Popover
3 - button
3
270 <div popover id=p4
>Inside popover
4</div>
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>
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');
308 <div popover id=p6
>Inside popover
6
309 <div style=
"height:2000px;background:lightgreen"></div>
312 <button popovertarget=p6
>Popover
6</button>
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
})
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');
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">
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 () => {
353 assert_true(popover7
.matches(':popover-open'),'invoking element should open popover');
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 () => {
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');
368 <div popover id=p8 anchor=p8anchor
>
369 <button tabindex=
"0">Button
</button>
370 <span id=inside8after
>Inside popover
8 after button
</span>
372 <button id=p8anchor
tabindex=
"0">Popover8 anchor (no action)
</button>
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'));
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');
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>
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>
400 <div popover id=convoluted_p3 anchor=convoluted_anchor
>Popover
3
401 <button popovertarget=convoluted_p4
>Open Popover
4</button>
403 <button onclick=
"convoluted_p1.showPopover()" tabindex=
"0">Open convoluted popover
</button>
405 #convoluted_p1 {top:
50px;}
406 #convoluted_p2 {top:
150px;}
407 #convoluted_p3 {top:
250px;}
408 #convoluted_p4 {top:
350px;}
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()');
465 <div popover id=p10
>Popover
</div>
466 <div popover=hint id=p11
>Hint
</div>
467 <div popover=manual id=p12
>Manual
</div>
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:
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.
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'));
500 assert_true(auto
.matches(':popover-open'));
501 assert_true(hint
.matches(':popover-open'));
502 // Clicking on the auto should close the hint:
504 assert_true(auto
.matches(':popover-open'),'auto should stay open');
505 assert_false(hint
.matches(':popover-open'),'hint should light dismiss');
507 assert_false(auto
.matches(':popover-open'));
508 },'Light dismiss of mixed popover types including hints');
511 <div popover id=p13
>Popover
1
512 <div popover id=p14
>Popover
2
513 <div popover id=p15
>Popover
3</div>
522 promise_test(async () => {
523 const p13
= document
.querySelector('#p13');
524 const p14
= document
.querySelector('#p14');
525 const p15
= document
.querySelector('#p15');
529 p15
.addEventListener('beforetoggle', (e
) => {
530 if (e
.newState
!== "closed")
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"');
543 <div id=p16 popover
>Popover
16
544 <div id=p17 popover
>Popover
17</div>
545 <div id=p18 popover
>Popover
18</div>
549 promise_test(async () => {
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
) => {
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"');
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>
573 promise_test(async () => {
576 const logEvents
= (e
) => {events
.push(`${e.newState==='open' ? 'show' : 'hide'} ${e.target.id}`)};
577 p19
.addEventListener('beforetoggle', (e
) => {
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"');