| |
| <!DOCTYPE html> |
| <meta charset="utf-8"> |
| <link rel=author href="mailto:masonf@chromium.org"> |
| <link rel=help href="https://open-ui.org/components/popup.research.explainer"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="resources/popup-utils.js"></script> |
| |
| <body> |
| <style> |
| .animation { opacity: 0; } |
| .animation:open { opacity: 1; } |
| .animation:not(:open) { animation: fade-out 1000s; } |
| @keyframes fade-out { |
| from { opacity: 1; } |
| to { opacity: 0; } |
| } |
| |
| .animation>div>div { left: 0; } |
| .animation:not(:open)>div>div { animation: rotate 1000s; color:red;} |
| @keyframes rotate { |
| from { transform: rotate(0); } |
| to { transform: rotate(360deg); } |
| } |
| |
| [popup] { top: 200px; } |
| [popup]::backdrop { background-color: rgba(255,0,0,0.2); } |
| </style> |
| |
| <script> |
| function createPopUp(t,type) { |
| const popUp = document.createElement('div'); |
| popUp.popUp = 'auto'; |
| popUp.classList = type; |
| const div = document.createElement('div'); |
| const descendent = div.appendChild(document.createElement('div')); |
| descendent.appendChild(document.createTextNode("Descendent element")); |
| popUp.append("This is a pop up",div); |
| document.body.appendChild(popUp); |
| t.add_cleanup(() => popUp.remove()); |
| return {popUp, descendent}; |
| } |
| promise_test(async (t) => { |
| const {popUp, descendent} = createPopUp(t,'animation'); |
| assert_false(isElementVisible(popUp)); |
| assert_equals(descendent.parentElement.parentElement,popUp); |
| assert_true(popUp.matches(':closed')); |
| assert_false(popUp.matches(':open')); |
| popUp.showPopUp(); |
| assert_false(popUp.matches(':closed')); |
| assert_true(popUp.matches(':open')); |
| assert_true(isElementVisible(popUp)); |
| assert_equals(popUp.getAnimations({subtree: true}).length,0); |
| popUp.hidePopUp(); |
| const animations = popUp.getAnimations({subtree: true}); |
| assert_equals(animations.length,2,'There should be two animations running'); |
| assert_false(popUp.matches(':open'),'popUp should not match :open as soon as hidden'); |
| assert_false(popUp.matches(':closed'),'popUp should not match :closed until animations complete'); |
| assert_true(isElementVisible(popUp),'but animations should keep the popUp visible'); |
| assert_true(isElementVisible(descendent),'The descendent should also be visible'); |
| await waitForRender(); |
| await waitForRender(); |
| assert_equals(popUp.getAnimations({subtree: true}).length,2,'The animations should still be running'); |
| assert_true(isElementVisible(popUp),'PopUp should still be visible due to animation'); |
| animations.forEach(animation => animation.finish()); // Force the animations to finish |
| await waitForRender(); // Wait one frame |
| assert_true(popUp.matches(':closed'),'The pop up should now match :closed'); |
| assert_false(popUp.matches(':open'),'The pop up still shouldn\'t match :open'); |
| assert_false(isElementVisible(popUp),'The pop up should now be invisible'); |
| assert_false(isElementVisible(descendent),'The descendent should also be invisible'); |
| assert_equals(popUp.getAnimations({subtree: true}).length,0); |
| },'Descendent animations should keep the pop up visible until the animation ends'); |
| |
| promise_test(async (t) => { |
| const {popUp, descendent} = createPopUp(t,''); |
| assert_equals(popUp.classList.length, 0); |
| assert_false(isElementVisible(popUp)); |
| popUp.showPopUp(); |
| assert_true(popUp.matches(':open')); |
| assert_true(isElementVisible(popUp)); |
| assert_equals(popUp.getAnimations({subtree: true}).length,0); |
| // Start an animation on the popUp and its descendent. |
| popUp.animate([{opacity: 1},{opacity: 0}],{duration: 1000000,iterations: 1}); |
| descendent.animate([{transform: 'rotate(0)'},{transform: 'rotate(360deg)'}],1000000); |
| assert_equals(popUp.getAnimations({subtree: true}).length,2); |
| // Then hide the popUp. |
| popUp.hidePopUp(); |
| assert_false(popUp.matches(':open'),'pop up should not match :open as soon as hidden'); |
| assert_true(popUp.matches(':closed'),'pop up should match :closed immediately'); |
| assert_equals(popUp.getAnimations({subtree: true}).length,2,'animations should still be running'); |
| await waitForRender(); |
| assert_equals(popUp.getAnimations({subtree: true}).length,2,'animations should still be running'); |
| assert_false(isElementVisible(popUp),'Pre-existing animations should not keep the pop up visible'); |
| },'Pre-existing animations should *not* keep the pop up visible until the animation ends'); |
| |
| promise_test(async (t) => { |
| const {popUp, descendent} = createPopUp(t,''); |
| popUp.showPopUp(); |
| assert_true(isElementVisible(popUp)); |
| assert_equals(popUp.getAnimations({subtree: true}).length,0); |
| let animation; |
| popUp.addEventListener('popuphide', () => { |
| animation = popUp.animate([{opacity: 1},{opacity: 0}],1000000); |
| }); |
| assert_equals(popUp.getAnimations({subtree: true}).length,0,'There should be no animations yet'); |
| popUp.hidePopUp(); |
| assert_equals(popUp.getAnimations({subtree: true}).length,1,'the hide animation should now be running'); |
| assert_true(!!animation); |
| assert_true(isElementVisible(popUp),'The animation should keep the popup visible'); |
| animation.finish(); |
| await waitForRender(); |
| assert_false(isElementVisible(popUp),'Once the animation ends, the popup is hidden'); |
| },'It should be possible to use the "popuphide" event handler to animate the hide'); |
| |
| |
| promise_test(async (t) => { |
| const {popUp, descendent} = createPopUp(t,''); |
| const dialog = document.body.appendChild(document.createElement('dialog')); |
| t.add_cleanup(() => dialog.remove()); |
| popUp.showPopUp(); |
| assert_true(isElementVisible(popUp)); |
| assert_equals(popUp.getAnimations({subtree: true}).length,0); |
| popUp.addEventListener('popuphide', () => { |
| popUp.animate([{opacity: 1},{opacity: 0}],1000000); |
| }); |
| assert_equals(popUp.getAnimations({subtree: true}).length,0,'There should be no animations yet'); |
| dialog.showModal(); // Force hide the popup |
| await waitForRender(); |
| assert_equals(popUp.getAnimations({subtree: true}).length,1,'the hide animation should now be running'); |
| assert_false(isElementVisible(popUp),'But the animation should *not* keep the popup visible in this case'); |
| },'It should *not* be possible to use the "popuphide" event handler to animate the hide, if the hide is due to dialog.showModal'); |
| |
| promise_test(async (t) => { |
| const {popUp, descendent} = createPopUp(t,''); |
| popUp.showPopUp(); |
| assert_true(isElementVisible(popUp)); |
| popUp.addEventListener('popuphide', (e) => { |
| e.preventDefault(); |
| }); |
| popUp.hidePopUp(); |
| await waitForRender(); |
| assert_false(isElementVisible(popUp),'Even if hide event is cancelled, the popup still closes'); |
| },'hide event cannot be cancelled'); |
| |
| promise_test(async (t) => { |
| const {popUp, descendent} = createPopUp(t,'animation'); |
| assert_false(isElementVisible(popUp)); |
| popUp.showPopUp(); |
| assert_false(popUp.matches(':closed')); |
| assert_true(popUp.matches(':open')); |
| assert_true(isElementVisible(popUp)); |
| assert_equals(popUp.getAnimations({subtree: true}).length,0); |
| popUp.popUp = 'manual'; |
| const animations = popUp.getAnimations({subtree: true}); |
| assert_equals(animations.length,2,'There should be two animations running'); |
| assert_false(popUp.matches(':open'),'popUp should not match :open as soon as hidden'); |
| assert_false(popUp.matches(':closed'),'popUp should not match :closed until animations complete'); |
| assert_true(isElementVisible(popUp),'but animations should keep the popUp visible'); |
| animations.forEach(animation => animation.finish()); // Force the animations to finish |
| await waitForRender(); // Wait one frame |
| assert_true(popUp.matches(':closed'),'The pop up should now match :closed'); |
| assert_false(popUp.matches(':open'),'The pop up still shouldn\'t match :open'); |
| assert_false(isElementVisible(popUp),'The pop up should now be invisible'); |
| },'Closing animations are triggered by changing the pop-up type'); |
| |
| promise_test(async (t) => { |
| const {popUp, descendent} = createPopUp(t,''); |
| popUp.showPopUp(); |
| assert_true(isElementVisible(popUp)); |
| assert_equals(popUp.getAnimations({subtree: true}).length,0); |
| popUp.addEventListener('popuphide', () => { |
| popUp.animate([{opacity: 1},{opacity: 0}],1000000); |
| }); |
| assert_equals(popUp.getAnimations({subtree: true}).length,0,'There should be no animations yet'); |
| popUp.hidePopUp(); |
| await waitForRender(); |
| assert_equals(popUp.getAnimations({subtree: true}).length,1,'the hide animation should now be running'); |
| assert_true(isElementVisible(popUp),'The popup should still be visible because the animation hasn\'t ended.'); |
| |
| popUp.dispatchEvent(new Event('finish')); |
| await waitForRender(); |
| assert_true(isElementVisible(popUp),'The popup should still be visible because the finish event was not trusted.'); |
| assert_equals(popUp.getAnimations({subtree: true}).length,1,'the hide animation should still be running'); |
| |
| popUp.dispatchEvent(new Event('cancel')); |
| await waitForRender(); |
| assert_true(isElementVisible(popUp),'The popup should still be visible because the cancel event was not trusted.'); |
| assert_equals(popUp.getAnimations({subtree: true}).length,1,'the hide animation should still be running'); |
| |
| popUp.remove(); |
| },'animation finish/cancel events must be trusted in order to finish closing the popup.'); |
| </script> |