From e1bdf62a5ea7a2c0c1265352991c388de05c6b6d Mon Sep 17 00:00:00 2001 From: Mason Freed Date: Mon, 24 Oct 2022 19:02:31 -0700 Subject: [PATCH] Add :closed pseudo class, for the pop-up API only Per the [1] resolution, the CSSWG will be adding both :open (which is already implemented for pop-up) and :closed (which this CL adds). At this point, both `:open` and `:closed` are only supported for the pop-up API, and only with the HTMLPopupAttribute feature enabled. More general support for other elements will be added later. [1] https://github.com/w3c/csswg-drafts/issues/7319#issuecomment-1276473056 Bug: 1307772 Change-Id: If0c27f1f8c7a30fc00656d5eeafe4b6c21bba890 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3961537 Reviewed-by: Joey Arhar Commit-Queue: Mason Freed Auto-Submit: Mason Freed Cr-Commit-Position: refs/heads/main@{#1063120} --- ...opup-animation-corner-cases.tentative.html | 27 +++++++++++++++++++ .../popup-attribute-basic.tentative.html | 15 +++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/html/semantics/popups/popup-animation-corner-cases.tentative.html b/html/semantics/popups/popup-animation-corner-cases.tentative.html index 917f0d900d1502..c463139b3c64ec 100644 --- a/html/semantics/popups/popup-animation-corner-cases.tentative.html +++ b/html/semantics/popups/popup-animation-corner-cases.tentative.html @@ -45,7 +45,10 @@ 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); @@ -53,6 +56,7 @@ 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(); @@ -61,6 +65,7 @@ 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'); @@ -82,6 +87,7 @@ // 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'); @@ -137,6 +143,27 @@ 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(); diff --git a/html/semantics/popups/popup-attribute-basic.tentative.html b/html/semantics/popups/popup-attribute-basic.tentative.html index de8d4d5f897003..e5b49ec9b19430 100644 --- a/html/semantics/popups/popup-attribute-basic.tentative.html +++ b/html/semantics/popups/popup-attribute-basic.tentative.html @@ -47,9 +47,11 @@ if (isVisible) { assert_not_equals(window.getComputedStyle(popUp).display,'none'); assert_equals(popUp.matches(':open'),isPopUp,`${message}: Visible pop-ups should match :open`); + assert_false(popUp.matches(':closed'),`${message}: Visible pop-ups and *all* non-pop-ups should *not* match :closed`); } else { assert_equals(window.getComputedStyle(popUp).display,'none',`${message}: Non-showing pop-ups should have display:none`); assert_false(popUp.matches(':open'),`${message}: Non-showing pop-ups should *not* match :open`); + assert_true(popUp.matches(':closed'),`${message}: Non-showing pop-ups should match :closed`); } } function assertIsFunctionalPopUp(popUp) { @@ -89,7 +91,7 @@ }); // Then loop through all HTML5 elements that render a box by default: - let elementsThatDontRender = ['audio','base','br','datalist','dialog','embed','head','link','meta','noscript','param','rp','script','style','template','title','wbr']; + let elementsThatDontRender = ['audio','base','br','datalist','dialog','embed','head','link','meta','noscript','param','rp','script','slot','style','template','title','wbr']; const elements = HTML5_ELEMENTS.filter(el => !elementsThatDontRender.includes(el)); elements.forEach(tag => { test((t) => { @@ -98,7 +100,13 @@ document.body.appendChild(element); t.add_cleanup(() => element.remove()); assertIsFunctionalPopUp(element); - }, `A <${tag}> element should behave as a pop-up.`); + }, `A <${tag} popup> element should behave as a pop-up.`); + test((t) => { + const element = document.createElement(tag); + document.body.appendChild(element); + t.add_cleanup(() => element.remove()); + assertNotAPopUp(element); + }, `A <${tag}> element should *not* behave as a pop-up.`); }); function createPopUp(t) { @@ -318,6 +326,7 @@ assert_false(isElementVisible(popUp)); popUp.showPopUp(); assert_true(popUp.matches(':open')); + assert_false(popUp.matches(':closed')); assert_true(getComputedStyle(popUp).opacity < 0.1,'Animations should start on show'); assert_throws_dom("InvalidStateError",() => popUp.showPopUp(),'Calling showPopUp on a popup that is in the process of animating show should throw InvalidStateError'); await finishAnimations(popUp); @@ -325,10 +334,12 @@ assert_true(isElementVisible(popUp)); popUp.hidePopUp(); assert_false(popUp.matches(':open')); + assert_false(popUp.matches(':closed'),'Not :closed until animations finish'); assert_true(getComputedStyle(popUp).opacity > 0.9,'Animations should start on hide'); assert_throws_dom("InvalidStateError",() => popUp.hidePopUp(),'Calling hidePopUp on a popup that is in the process of animating hide should throw InvalidStateError'); popUp.showPopUp(); // But showPopUp should still be ok. popUp.hidePopUp(); // Clean up await finishAnimations(popUp); + assert_true(popUp.matches(':closed'),':closed should match once animations finish'); },'Exceptions are thrown even when show/hide are animated')