blob: c463139b3c64ece7b7807042b7324b0aec742084 [file] [log] [blame]
<!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>