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>