popover-top-layer-combinations.html (6530B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <title>Popover combined with dialog/fullscreen behavior</title> 4 <link rel=author href="mailto:masonf@chromium.org"> 5 <link rel=help href="https://open-ui.org/components/popover.research.explainer"> 6 <link rel=help href="https://html.spec.whatwg.org/multipage/popover.html"> 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="/common/top-layer.js"></script> 13 <script src="resources/popover-utils.js"></script> 14 15 <button id=visible>Visible button</button> 16 <div id=examples> 17 <dialog popover>Popover Dialog</dialog> 18 <dialog popover open style="top:50px;">Open Non-modal Popover Dialog</dialog> 19 <div popover class=fullscreen>Fullscreen Popover</div> 20 <dialog popover class=fullscreen>Fullscreen Popover Dialog</dialog> 21 <dialog popover open class=fullscreen style="top:200px;">Fullscreen Open Non-modal Popover Dialog</dialog> 22 </div> 23 24 <style> 25 [popover] { 26 inset:auto; 27 top:0; 28 left:0; 29 } 30 [popover].fullscreen.visible { 31 display:block; 32 } 33 </style> 34 35 <script> 36 const isDialog = (ex) => ex instanceof HTMLDialogElement; 37 const isFullscreen = (ex) => ex.classList.contains('fullscreen'); 38 function ensureIsOpenPopover(ex,message) { 39 // Because :popover-open will eventually support <dialog>, this does extra work to 40 // verify we're dealing with an :popover-open Popover. Note that this will also throw 41 // if this is an element with the `popover` attribute that has been made 42 // visible via an explicit `display:block` style rule. 43 message = message || 'Error'; 44 assert_true(ex.matches(':popover-open'),`${message}: Popover doesn\'t match :popover-open`); 45 ex.hidePopover(); // Shouldn't throw if this is a showing popover 46 ex.showPopover(); // Show it again to avoid state change 47 assert_true(ex.matches(':popover-open'),`${message}: Sanity`); 48 } 49 window.onload = () => requestAnimationFrame(() => requestAnimationFrame(() => { 50 const examples = Array.from(document.querySelectorAll('#examples>*')); 51 examples.forEach(ex => { 52 promise_test(async (t) => { 53 t.add_cleanup(() => ex.remove()); 54 // Test initial conditions 55 if (ex.hasAttribute('open')) { 56 assert_true(isDialog(ex)); 57 assert_true(isElementVisible(ex),'Open dialog should be visible by default'); 58 ex.showPopover(); // Should not throw 59 ex.removeAttribute('open'); 60 ex.hidePopover(); 61 assert_false(isElementVisible(ex),'Removing the open attribute should hide the dialog'); 62 } else { 63 ex.showPopover(); // Should not throw 64 ensureIsOpenPopover(ex,'showPopover should work'); 65 ex.hidePopover(); // Should not throw 66 assert_false(ex.matches(':popover-open'),'hidePopover should work'); 67 } 68 assert_false(isElementVisible(ex)); 69 70 // Start with popover, try the other API 71 ex.showPopover(); 72 ensureIsOpenPopover(ex); 73 let tested_something=false; 74 if (isDialog(ex)) { 75 tested_something=true; 76 assert_throws_dom("InvalidStateError",() => ex.showModal(),'Calling showModal() on an already-showing Popover should throw InvalidStateError'); 77 ex.show(); // Should not throw 78 ex.close(); 79 ex.showPopover(); 80 } 81 if (isFullscreen(ex)) { 82 tested_something=true; 83 let requestSucceeded = false; 84 await blessTopLayer(ex); 85 await ex.requestFullscreen() 86 .then(() => {requestSucceeded = true;}) // We should not hit this. 87 .catch((exception) => { 88 // This exception is expected. 89 assert_equals(exception.name,'TypeError',`Invalid exception from requestFullscreen() (${exception.message})`); 90 }); 91 assert_false(requestSucceeded,'requestFullscreen() should not succeed when the element is an already-showing Popover'); 92 } 93 assert_true(tested_something); 94 ensureIsOpenPopover(ex); 95 ex.hidePopover(); 96 97 // Start with the other API, then try popover 98 if (isDialog(ex)) { 99 ex.show(); 100 assert_true(ex.hasAttribute('open')); 101 ex.showPopover(); // Should not throw 102 ex.close(); 103 assert_false(ex.hasAttribute('open')); 104 ex.hidePopover(); 105 ex.showModal(); 106 assert_true(ex.hasAttribute('open')); 107 assert_throws_dom("InvalidStateError",() => ex.showPopover(),'Calling showPopover() on an already-showing modal dialog should throw InvalidStateError'); 108 ex.close(); 109 assert_false(ex.hasAttribute('open')); 110 ex.hidePopover(); 111 } else if (isFullscreen(ex)) { 112 let requestSucceeded = false; 113 await blessTopLayer(visible); 114 await ex.requestFullscreen() 115 .then(() => { 116 assert_throws_dom("InvalidStateError",() => ex.showPopover(),'Calling showPopover() on an already-fullscreen element should throw InvalidStateError'); 117 }); 118 await document.exitFullscreen() 119 .then(() => assert_true(true)); 120 } 121 122 // Finally, try invoking these combined popovers via a declarative invoker 123 const button = document.createElement('button'); 124 t.add_cleanup(() => button.remove()); 125 document.body.appendChild(button); 126 button.popoverTargetElement = ex; 127 button.popoverTargetAction = "toggle"; 128 assert_false(ex.matches(':popover-open')); 129 await clickOn(button); 130 ensureIsOpenPopover(ex,'Invoking element should be able to invoke all popovers'); 131 ex.hidePopover(); 132 if (isDialog(ex)) { 133 ex.showModal(); 134 assert_true(ex.hasAttribute('open')); 135 } else if (isFullscreen(ex)) { 136 // Popover fullscreen isn't visible by default, so explicitly add 137 // display:block, so that calls to "clickOn" can succeed. 138 ex.classList.add('visible'); 139 await blessTopLayer(ex); 140 await ex.requestFullscreen(); 141 } else { 142 assert_unreached('Not a dialog or fullscreen'); 143 } 144 ex.appendChild(button); // Add button to the element, so it's visible to click 145 await clickOn(button); 146 assert_false(ex.matches(':popover-open'),'The invoker click should have failed on the already-open dialog/fullscreen'); 147 if (isDialog(ex)) { 148 ex.close(); 149 } else { 150 await document.exitFullscreen() 151 } 152 }, `Popover combination: ${ex.textContent}`); 153 }); 154 })); 155 </script>