html icon indicating copy to clipboard operation
html copied to clipboard

Fix user-agent style rules for top layer transitions

Open josepharhar opened this issue 1 year ago • 30 comments

With the new css-position spec and overlay CSS property, it is possible for popovers, dialogs, and their ::backdrops to be in the top layer while transitioning from showing to hidden. During this time, :popover-open and :modal don't apply, but we still want the user-agent styles to apply as if these elements were still showing in the top layer. This PR accomplishes this by moving these styles from selectors with :modal or :popover-open to a definition which says that the element is in the top layer. This is implemented in chromium with an internal pseudo-selector.

These rules can't be accomplished with existing selectors and pseudo-selectors because there is no way to select an element which is transitioning to closed but is still being rendered in the top layer.

  • [ ] At least two implementers are interested (and none opposed):
    • Chrome
  • [x] Tests are written and can be reviewed and commented upon at:
    • https://github.com/web-platform-tests/wpt/pull/40369
  • [ ] Implementation bugs are filed:
    • Chromium: https://chromium-review.googlesource.com/c/chromium/src/+/4585724
    • Gecko: …
    • WebKit: …
  • [ ] MDN issue is filed: …

(See WHATWG Working Mode: Changes for more details.)


/infrastructure.html ( diff ) /rendering.html ( diff )

josepharhar avatar Jun 05 '23 21:06 josepharhar

@josepharhar Can you solve the issue by just re-writing the UA styles to be this?

/* common to fullscreen, dialog, popover */
::backdrop {
    display: block;
    position: fixed;
    inset: 0;
}

/* fullscreen */
:not(:root):fullscreen::backdrop {
    background: black;
}

/* modal dialogs */
dialog:not([popover])::backdrop,
dialog[popover]:modal::backdrop {
    background: rgba(0, 0, 0, 0.1);
}

/* popovers that aren't fullscreen or modal dialogs */
[popover]:not(:modal)::backdrop {
    pointer-events: none;
}

nt1m avatar Jun 07 '23 04:06 nt1m

I'm defining the modal pseudo class here: https://github.com/whatwg/html/pull/9395

nt1m avatar Jun 07 '23 07:06 nt1m

cc @mfreed7 @lilles

nt1m avatar Jun 07 '23 19:06 nt1m

@josepharhar Can you solve the issue by just re-writing the UA styles to be this?

/* common to fullscreen, dialog, popover */
::backdrop {
    display: block;
    position: fixed;
    inset: 0;
}

/* fullscreen */
:not(:root):fullscreen::backdrop {
    background: black;
}

/* modal dialogs */
dialog:not([popover])::backdrop,
dialog[popover]:modal::backdrop {
    background: rgba(0, 0, 0, 0.1);
}

/* popovers that aren't fullscreen or modal dialogs */
[popover]:not(:modal)::backdrop {
    pointer-events: none;
}

I think that works. Curious though, can't the dialog rules just be this:

dialog:modal::backdrop {
    background: rgba(0, 0, 0, 0.1);
}

It can only be :modal, even if it matches [popover], if it got there via showModal(). Right?

mfreed7 avatar Jun 07 '23 20:06 mfreed7

I think that works. Curious though, can't the dialog rules just be this:

dialog:modal::backdrop {
    background: rgba(0, 0, 0, 0.1);
}

It can only be :modal, even if it matches [popover], if it got there via showModal(). Right?

I mostly wrote it that way in case you want the rule to match during a transition, but yeah dialog:modal::backdrop works too if that doesn't matter too much.

nt1m avatar Jun 07 '23 21:06 nt1m

Quickly looking at the implementation in Chrome, :modal does not match after close() going through the transition.

lilles avatar Jun 07 '23 21:06 lilles

Quickly looking at the implementation in Chrome, :modal does not match after close() going through the transition.

dialog:not([popover])::backdrop,
dialog[popover]:modal::backdrop {
    background: rgba(0, 0, 0, 0.1);
}

would cover this case.

nt1m avatar Jun 07 '23 21:06 nt1m

dialog:not([popover])::backdrop,

Oh, clever, that's why you wrote it that way. I think there's still a problem though: what if you have <dialog popover> and you do dialog.showModal(). And you transition the closing of the dialog. Then during dialog.close(), neither of those selectors will match. (The top one won't because it does have [popover] but it just isn't being used at the moment.)

I think you do actually need a pseudo state for "is a popover in the top layer", which is what this PR tries to do.

mfreed7 avatar Jun 07 '23 23:06 mfreed7

Oh, clever, that's why you wrote it that way. I think there's still a problem though: what if you have

and you do dialog.showModal(). And you transition the closing of the dialog. Then during dialog.close(), neither of those selectors will match. (The top one won't because it does have [popover] but it just isn't being used at the moment.)

Calling showModal() on a <dialog popover> is a pattern I'd be ok not supporting tbh. It already leads to weird behavior when you try to light dismiss the popover modal dialog since the page would remain inert.

nt1m avatar Jun 08 '23 02:06 nt1m

In any case, I think it would be worth looking into cleaning up the UA styles a bit.

nt1m avatar Jun 08 '23 02:06 nt1m

~~[popover]:not(:modal)::backdrop and [popover]:popover-open::backdrop aren't working for the overlay-transition-backdrop-entry.html test I'm adding: https://github.com/web-platform-tests/wpt/pull/40369/files#diff-9dc3657353b3d04b1a244272b418d008cc6773665b6e7ff0e096dd35543446b3~~

~~We need to stop matching the user-agent styles to ::backdrop when the popover is showing but its entrance into the top layer has been delayed with overlay. This does not have any interactions with dialog elements.~~

Nevermind, that test is a work in progress and was not working with the changes for this PR anyways

josepharhar avatar Jun 13 '23 20:06 josepharhar

Calling showModal() on a <dialog popover> is a pattern I'd be ok not supporting tbh. It already leads to weird behavior when you try to light dismiss the popover modal dialog since the page would remain inert.

I'd be supportive of that also, but it unfortunately doesn't solve the problem. You could do showModal() and then add [popover] after that. And in that case the UA style rules are still broken. So I don't think we should prohibit this case, since it doesn't solve anything.

We used to have a "transitioning" state explicitly in the spec, but it was unfortunately removed due to pushback. But what we really have here is a state that represents "transitioning". The UA style rules need to apply during transitions. To do that, without exposing the "transitioning" state to JS, we need an internal pseudo class and the equivalent in spec text (i.e., this PR).

mfreed7 avatar Jun 15 '23 16:06 mfreed7

/* common to fullscreen, dialog, popover */
::backdrop {
    display: block;
    position: fixed;
    inset: 0;
}

:not(:root):fullscreen::backdrop {
    background: black;
}

dialog:modal::backdrop {
    background: rgba(0, 0, 0, 0.1);
}

[popover]:popover-open::backdrop {
    pointer-events: none;
}

@mfreed7 @josepharhar Here's another idea, it sounded like the Chromium bug report was about the popover & dialog ::backdrop styles not having the same lifespan wrt transitions.

The styles above would make it as short for both dialog & popover ^

If the author wants to preserve the ::backdrop styles during the transition, they can add a transition to the backdrop or unconditionally add the background there.

nt1m avatar Jun 17 '23 05:06 nt1m

Quickly looking at the implementation in Chrome, :modal does not match after close() going through the transition.

dialog:not([popover])::backdrop,
dialog[popover]:modal::backdrop {
    background: rgba(0, 0, 0, 0.1);
}

would cover this case.

This could fix the backdrop, but the user-agent styles for the dialog element itself which keep it in the center of the screen also need to stay applied while animating out, during which time the dialog does not match :modal: https://bugs.chromium.org/p/chromium/issues/detail?id=1451910

I suppose that in order to fix this, we could move the positioning related styles from the dialog:modal selector to the dialog selector, which would also make it match while the dialog is closed... or add an internal pseudo selector which matches when an element is in the top layer and use that instead.

Edit: Moving the whole thing from dialog:modal to dialog won't work since then it would also match non-modal dialogs

josepharhar avatar Jun 20 '23 23:06 josepharhar

I pushed some commits for similar fixes for the dialog element, which I learned about and am fixing here: https://bugs.chromium.org/p/chromium/issues/detail?id=1451910

josepharhar avatar Aug 03 '23 21:08 josepharhar

@nt1m What do you think of the current state of this PR? I'd like to get multi-implementer interest

josepharhar avatar Aug 14 '23 17:08 josepharhar

Sorry for coming in late, but if the existing pseudoclasses aren't sufficient for UA usage, does that indicate they'll similarly be insufficient for author usage? I'm not sure I see why an author would be okay with their :modal styles no longer applying while the element is still acting modal-ish. Am I missing something that makes these phases okay to only be targetable by the UA itself?

If not, the correct path would be to fix the definitions of the pseudoclasses, to match what they actually need to.

tabatkins avatar Aug 25 '23 21:08 tabatkins

Yeah, we could make the internal pseudoclass a real pseudoclass for authors too. It just didn't seem like an important use case to provide for authors

josepharhar avatar Aug 26 '23 00:08 josepharhar

The UA style rules need to apply during transitions.

A different way to make that happen is to call out the relevant properties in transition-property, and give them a step-end easing function--basically handling them the same way that was proposed for the overlay property itself.

The main awkwardness with this that authors need to remember what properties are involved--but we could solve that by providing a keyword that functions just like a shorthand, expanding out to the relevant set of properties. For example, something like ua-modal-dialog would expand out to overlay, overflow, inset, etc.

The author can then easily control these properties together as one unit; or split out individual ones by listing them individually later in the list, just like for regular shorthands (e.g. transition: border ease, border-style step-start).

(We could maybe even go a step further and add a generic keyword that computes to the correct set of properties depending on the element involved.)

Note: Another solution would be to invent additive cascade and have the UA default stylesheet include these properties together with a step-end easing function, so that authors can just add to the list; but that's a much larger project than I suspect we want to tackle here. :)

fantasai avatar Aug 31 '23 18:08 fantasai

I see how are are kind of requiring authors to transition overlay, but I'd rather not add even more stuff for authors to include to make the exit animation reasonable.

If we end up deprecating and removing non-modal dialog.show(), then we could make these user-agent styles also apply when the dialog is in any state and then none of this stuff would be needed anymore. I'm not sure if this would also work with popover but it might - I'm going to go test it out. EDIT: I tested it out and I think its fine except that <dialog popover>s can't differentiate between whether they should have popover styles or dialog styles during the transition out since :modal and :popover-open are both gone. Maybe this is fine though? Or we could have an internal selector to make this differentiation.

josepharhar avatar Sep 04 '23 12:09 josepharhar

I think for web developers it would not be that much more work. Instead of overlay you'd either use overlay-dialog or overlay-popover and those would expand to the relevant properties. The upside of that is that we don't end up with magical unexplained states only the user agent gets to style.

annevk avatar Sep 06 '23 07:09 annevk

What is the current status on this? I haven't read all of the above, but just trying to ask some general questions to get things moving:

@josepharhar, do you believe the current PR draft represents the best proposal at this point, in light of the above discussions? If so, @annevk @tabatkins @nt1m what do you think of it?

domenic avatar Jan 25 '24 23:01 domenic

Sorry for coming in late, but if the existing pseudoclasses aren't sufficient for UA usage, does that indicate they'll similarly be insufficient for author usage? I'm not sure I see why an author would be okay with their :modal styles no longer applying while the element is still acting modal-ish. Am I missing something that makes these phases okay to only be targetable by the UA itself?

If not, the correct path would be to fix the definitions of the pseudoclasses, to match what they actually need to.

Yeah I suppose that we could try making :modal match while the dialog is in the top layer rather than matching while its modal flag is true. I haven't worked on this in some time but I imagine that could be all we need. Does that sound ok to everyone?

josepharhar avatar Jan 27 '24 01:01 josepharhar

Yeah I suppose that we could try making :modal match while the dialog is in the top layer rather than matching while its modal flag is true. I haven't worked on this in some time but I imagine that could be all we need. Does that sound ok to everyone?

I don't have strong opinions, but one thing to note: the "modal" nature of the dialog goes away immediately. The only thing that is preserved during an animation is the top layer status. In that sense, :modal not matching immediately sounds technically correct.

mfreed7 avatar Jan 27 '24 03:01 mfreed7

How does that allow you to style the transition? I'm not seeing it. I still think we need something akin to what @fantasai wrote above and ideally the CSS WG does a thorough review of it first.

annevk avatar Feb 05 '24 15:02 annevk

I created a csswg issue to discuss: https://github.com/w3c/csswg-drafts/issues/9912

josepharhar avatar Feb 05 '24 18:02 josepharhar

@josepharhar @mfreed7 I actually wonder if the UA styles could be transition-property: overlay, overflow, inset, etc. by default, but with the transition-duration set to 0. On the author level, they would just need to override duration if they want.

We could also have an additive CSS solution transition-property: revert-layer, additional-properties-to-transition

nt1m avatar Feb 05 '24 20:02 nt1m

I actually wonder if the UA styles could be transition-property: overlay, overflow, inset, etc. by default, but with the transition-duration set to 0. On the author level, they would just need to override duration if they want.

If the author uses transition or transition-property it would override these, right? So If I wanted to transition opacity for 1 second, then all of these UA styles wouldn't apply anymore and I'd have to also add overflow inset etc. again right?

josepharhar avatar Feb 05 '24 20:02 josepharhar

I actually wonder if the UA styles could be transition-property: overlay, overflow, inset, etc. by default, but with the transition-duration set to 0. On the author level, they would just need to override duration if they want.

If the author uses transition or transition-property it would override these, right? So If I wanted to transition opacity for 1 second, then all of these UA styles wouldn't apply anymore and I'd have to also add overflow inset etc. again right?

Yeah hence the suggestion for additive CSS: transition: revert-layer 0.3s, translate 0.2s, which would also apply the properties from the UA sheet.

nt1m avatar Feb 05 '24 20:02 nt1m

The revert-layer bit would need to be a CSSWG addition

nt1m avatar Feb 05 '24 20:02 nt1m