Nothing Special   »   [go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-position-3] Containing block formed by fragmented inlines #8284

Open
fantasai opened this issue Jan 6, 2023 · 8 comments
Open

[css-position-3] Containing block formed by fragmented inlines #8284

fantasai opened this issue Jan 6, 2023 · 8 comments
Labels
Agenda+ Later Lower-priority items that are flagged for CSSWG discussion css-position-3 Current Work Needs Testcase (WPT)

Comments

@fantasai
Copy link
Collaborator
fantasai commented Jan 6, 2023

CSS2.1 defines the containing block formed by an inline box like this:

In the case that the ancestor is an inline element, the containing block is the bounding box around the padding boxes of the first and the last inline boxes generated for that element.
In CSS 2.1, if the inline element is split across multiple lines, the containing block is undefined. 

In CSS Positioning Level 3, we tried to generalize the definition in a way that would handle multiple lines:

If the ancestor is an inline box, the containing block is formed by the block-start and inline-start content edges of the first box fragment of the ancestor, and the block-end and inline-end content edges of the last box fragment of the ancestor.

This basically takes (for LTR text) the top/left edges of the first fragment and the bottom/right edges of the last fragment and makes a rectangle of them. The “left” box edge might end up as the “right” containing block edge, but in any case there's a rectangle.

What browsers actually do is demonstrated in this testcase:

  • Firefox takes the padding area of the first line's fragment only.
  • WebKit/Blink start at the top/left corner of the first line's fragment, then extend to the bottom edge of the last line's fragment, and extend it rightwards only as far as the right edge of the last line's fragment. If the last line's fragment's right edge is to the left, the containing block is zero-width.

The question here is, what actually is the most useful thing to do?

  • Use the first line's fragments only (so the entire containing block is contained by its generating box).
  • Anchor at the start/start corner of the first line box and extend to the end/end corner of the last line box, but clamp the inline size at zero (so the end/end corner might not overlap with a fragment).
  • Anchor at the start/start corner of the first line box and extend to the end/end corner of the last line box, and allow inversions (so the corners are pinned to actual fragment corners, but the containing block might end up between fragments).
  • Anchor at the start/start corner of the first line box and extend to the end/end corner of either the last line box (if that's endward of the start line) or of the first line box (otherwise).
  • Something else?
@fantasai
Copy link
Collaborator Author
fantasai commented Jan 6, 2023

This was last discussed in #609 ; see also dbaron's multi-directional test suite.

@tabatkins
Copy link
Member

(And the spec now reflects the resolution from #609, which is to just take Chrome's behavior. We're just not completely convinced it's the best behavior.)

@bernhardf-ro
Copy link

We discussed this and while, as expected, we didn't come up with any simple solutions, I'll summarize our opinions and ideas:

We broke the containing block down into it's four coordinates:

inline-start: the start edge of the first visual fragment of the first line (equal to specified single line behavior)
block-start: the start edge of the first visual fragment of the first line (equal to specified single line behavior)
inline-end: the line box end edge* of the first line (basically extend the fragment to the end, similar to block fragments being extended to the end of pages or columns)
block-end: either the end edge of the last visual fragment of the last line (equal to specified single line behavior). Alternatively, though not preferred by us, the end edge of the first line** (Firefox-like).

*"line box end edge" could be defined in different ways with regard to how it interacts with floats.
It could ignore floats, i.e. use the inline-end content edge of the inline formatting context box.
Alternatively floats could be taken into account, either just the ones affecting the first line or (if block-end uses the last line) all affecting any of the lines, i.e. using the maximum or minimum of the line ends.

**"end edge of the first line" is something we were not able to define. Using the end edge of the last fragment of the first line didn't seem right, as we already actively ignore that fragment for inline-end.
First fragment, fragment bounding box and line box all didn't feel right either. (Using the last line instead seems to solve that, though.)

One colleague brought up a more out-of-the-box approach that we feel worth discussing:

To enable anchoring boxes both at the (visual) start as well as the end of the multi-line inline in all cases a conditional behavior could be introduced.
Let's say the condition is handled via a property with the values: start (initial), end and auto. auto computes to end when inset-inline-start is auto and inset-inline-end is non-auto, and to start otherwise.
The property isn't necessary as the auto behavior could always be used. But this might be to big of a change and a too far reaching effect of different inset-inline-* values.
If the condition is start the behavior is Firefox-like. If it is end it is as well, but uses the last line instead of the first, so inset-inline-end is actually relative to the visual end of the multi-line inline, like for a single line inline.
(Our approach of extending inline fragments to the line edge at their split points could be combined with this approach.)

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-position-3] Containing block formed by fragmented inlines, and agreed to the following:

  • RESOLVED: Try out the proposal above, spec the fragmentation behavior
  • RESOLVED: in LTR-tb the top-left corner is the top-left corner of the top-left-most fragment of the first line, and the bottom and right edges are bottommost and rightmost edges of all the fragments. Directionality comes from the inline
The full IRC log of that discussion <emilio> scribenick: emilio
<fantasai> https://www.software.hixie.ch/utilities/js/live-dom-viewer/?%3C!DOCTYPE%20html%3E%0A%3Cp%3E%3Cdiv%20style%3D%22background%3A%20gray%3B%20width%3A%20100px%3B%20height%3A%201em%3B%20display%3A%20inline-block%22%3E%3C%2Fdiv%3E%3Cspan%20style%3D%22position%3A%20relative%3B%20border%3A%20solid%20blue%22%3E%3Cspan%20style%3D%22position%3A%20absolute%3B%20inset%3A%200%3B%20border%3A%20orange%20solid%22%3E%3C%2Fspan
<fantasai> %3Efirst%20and%3Cbr%3Esecond%20line%20that%27s%20very%20long%20and%3Cbr%3Ethird%3C%2Fspan%3E%0A%3Cp%3E%3Cspan%20style%3D%22position%3A%20relative%3B%20border%3A%20solid%20blue%22%3E%3Cspan%20style%3D%22position%3A%20absolute%3B%20inset%3A%200%3B%20border%3A%20orange%20solid%22%3E%3C%2Fspan%3Efirst%20and%3Cbr%3Esecond%20line%20that%27s%20very%20long%20and%3Cbr%3Ethird%3C%2Fspan%3E
<emilio> fantasai: what do we do when the absolute positioned box is an inline but it spans multiple lines
<emilio> [describes test-case]
<emilio> ... firefox and chrome disagree
<emilio> ... so... on a single line the abspos cb is the line's bounds
<emilio> ... when it spans multiple lines we could use join the top-left of the first fragment and the bottom-right of the last
<emilio> ... but if the last line is to the left of the first it creates a negative area
<emilio> ... chrome / webkit collapse the width to zero
<emilio> ... firefox does [missing]
<emilio> ... there are pros and cons to both approaches
<emilio> ... the four things we think we can do are in the bullet list in the last comment
<fantasai> Use the first line's fragments only (so the entire containing block is contained by its generating box).
<fantasai> Anchor at the start/start corner of the first line box and extend to the end/end corner of the last line box, but clamp the inline size at zero (so the end/end corner might not overlap with a fragment).
<fantasai> Anchor at the start/start corner of the first line box and extend to the end/end corner of the last line box, and allow inversions (so the corners are pinned to actual fragment corners, but the containing block might end up between fragments).
<fantasai> Anchor at the start/start corner of the first line box and extend to the end/end corner of either the last line box (if that's endward of the start line) or of the first line box (otherwise).
<dbaron> There's some old discussion of this in https://bugzilla.mozilla.org/show_bug.cgi?id=489100 and maybe also the somewhat-related https://bugzilla.mozilla.org/show_bug.cgi?id=489207
<emilio> TabAtkins: my preferred behavior is to anchor the top left of top left of the first fragment, anchor bottom to the bottom of the last fragment, and anchor right to the right-edge of either the whole fragment, or line box edge if there are multiple
<emilio> ... allows cb to be as tall as the whole multi-line text box
<emilio> ... and also as wide as the whole text
<emilio> ... if there's text going further on later lines it'd be good to include that on the box left
<emilio> ... so top-left to top-left of first fragment (matches webkit + blink + ff)
<iank_> q+
<emilio> ... bottom matching blink/webkit
<emilio> ... and right edge to the right edge of line box (if multi-line) or end of fragment if it's single-fragment
<astearns> ack dbaron
<fantasai> ... rather than whereever the first fragment happened to break
<emilio> dbaron: this is something I thought about a good bit about a decade ago
<fantasai> Tab's proposal seems reasonable to me...
<emilio> ... two things I wanna point out
<emilio> ... one is that it's worth being reasonably careful about directionality
<emilio> ... I think we all do this based on the direction prop of the inline
<iank_> we've fixed stuff
<emilio> ... iirc chrome didn't handle rtl well but edge did
<emilio> ... related issue issue is: how do auto-offsets behave for absposes inside inlines
<emilio> ... maybe it's not that related but there are a few interesting aspects to it
<astearns> ack iank_
<astearns> q+
<emilio> iank_: worth retesting Chrome because we've fixed a lot of these
<emilio> ... one thing to note is the start and end is complicated, because the IFC can have different direction to the inline CB
<emilio> ... I _believe_ we use the direction of the IFC
<TabAtkins> Yeah, I'd think the IFC is probably the most coherent thing to take the directions from?
<emilio> ... we don't necessarily want to do that but it makes sense given all the edge cases
<emilio> ... when you say linebox we don't quite want that
<emilio> ... you probably want the max IFC inline edge coordinate
<emilio> ... because you can have multiple fragments in one line
<emilio> ... and those might not go at the end of the line box
<emilio> TabAtkins: if the inline starts at the left edge of the text area and it covers multiple lines the CB should fill the box
<emilio> ... even if if the actual text bounds is slightly narrower
<emilio> iank_: you can have the situation where the span won't go all the way to the inline edge but there may be some text there
<emilio> ... but it is different
<emilio> ... I think tab's proposal seems fine
<emilio> astearns: I don't like something about tab's proposal, which is the difference for the determination for the start and end edge
<emilio> ... I think it'd make sense to also take the smallest start edge
<emilio> iank_: I doubt that'd be compatible
<emilio> ... that's interoperable now, it's used to do something like a caret at the start of an inline
<emilio> TabAtkins: that's why
<emilio> astearns: It's just a weird result where the orange box covers half of the paragraph in fantasai's
<astearns> ack dbaron
<emilio> ... test-case
<astearns> ack astearns
<emilio> TabAtkins: yeah, but it's very interoperable behavior, so I think we're stuck with that
<iank_> there is :)
<emilio> dbaron: Are there complications here when the inline is split across columns/pages?
<emilio> iank_: there are, I can talk about what edge did here, but that's a long tangent
<emilio> TabAtkins: so we do ???
<astearns> ack dbaron
<TabAtkins> s/???/still anchor the top-left of the CB to the top-left of the first fragment there?/
<emilio> dbaron: I think the issue of splitting over columns / pages is one of the reasons this never got fixed in Gecko
<astearns> ack fantasai
<Zakim> fantasai, you wanted to discuss this right-edge criteria
<emilio> ... but might also be more about the auto behavior
<astearns> q+
<emilio> fantasai: Wanted to talk about about what we pin the right edge to
<emilio> ... end of the first line's last fragment?
<emilio> ... rightmost of all of the lineboxes?
<TabAtkins> My instinct is that if there's a column/etc break, we put the bottom edge to the end of the last fragment in that fragmentainer, analogous to the right-edge behavior.
<emilio> ... rightmost of all the fragments?
<emilio> ... line boxes have different edges with floats
<iank_> From my perspective right most of either the line-boxes or the fragments
<emilio> ... do we want to consider all of the lines? Or just the first?
<emilio> iank_: pretty strong bias towards all of the lines
<emilio> ... covers the case where all of the lines might not line up
<TabAtkins> Ah, I see what you mean by "probably don't want lineboxes" then, yeah
<TabAtkins> right edge fo the IFC or whatever, instead, yeah
<TabAtkins> +1 to rightmost/bottommost of all fragments
<emilio> fantasai: my proposal would be to use the rightmost edge of all of the fragments of the inline box
<emilio> astearns: hard time coming with a use case for doing much more than what FF is doing
<emilio> ... the case of putting a caret at the beginning of the line is the thing we can't change
<emilio> ... what use case is there for having an abspos thing going over some weird section of a fragmented inline?
<emilio> TabAtkins: there's no use case for doing both of the horizontal/vertical bounds we're defining
<emilio> ... but each individually make sense
<emilio> iank_: someone willing to cover all of the lines
<emilio> fantasai: that's only useful if the span is the first thing in the line
<TabAtkins> 1) Putting something at the start of the text (requires top/left to be the top left of the first fragment). 2) Have something as tall as the text (background, or overlay, etc).
<emilio> iank_: true
<TabAtkins> I think those are the two use-cases we can reasoanbly address with a single-box solution.
<emilio> astearns: I worry that we're coming up with this thing that isn't really motivated by use cases
<emilio> ... we're defining some reasonable behavior for this edge case where a lot of cases we could get the abspos cb further up
<emilio> ... firefox's behavior seems simpler to spec and doesn't run into fragmentation issues
<TabAtkins> +1
<emilio> iank_: we have a solution for fragmentation
<TabAtkins> (+1 to Ian, that is)
<emilio> ... but somewhat hard
<emilio> ... probably don't have time on this call to consider it
<emilio> ... I think we should consider at least all of the lines' block edges
<iank_> (i've got to run 👋)
<emilio> fantasai: proposal is that that in LTR-tb the top-left corner is the top-left corner of the top-left-most fragment of the first line, and the bottom and right edges are bottommost and rightmost edges of all the fragments
<dholbert> bottom right of all-of-the-fragments, or all-of-the-line-boxes-that-contain-fragments? (if there's a <br> that prevents the fragments from hitting the end of the line)
<emilio> RESOLVED: Try out the proposal above, spec the fragmentation behavior
<TabAtkins> (afaict, the use-cases I listed are already satisfied by Chrome/WebKit's behavior, since neither of them depend on the right side. But I do think a zero-width box should be avoided if we can.)
<dbaron> (also need to be clear whether that LTR-tb is for the IFC or the inline)
<fantasai> analogously for other writing modes, anchoring on the same writing mode as the painting of borders
<emilio> fantasai: I think you want the writing-mode of the inline element, e.g. if you want a flag at the start of the span
<emilio> emilio: iank_ was arguing for writing-mode of the IFC
<dholbert> (my question relates to a distinction that Tab was making early on; we should be clear whether we're talking about line boxes vs. fragments for bottom-right corner. Sorry, my audio seems to be broken)
<emilio> proposal for dholbert's question is using the fragments themselves
<dholbert> thanks!
<emeyer> I have an uneasy feeling about the proposed solution, but I’m not sure I understand it properly..
<fantasai> Using fragments, because if you have short lines of text you want the edges of those inlines, not the far right of the screen
<emilio> RESOLVED: in LTR-tb the top-left corner is the top-left corner of the top-left-most fragment of the first line, and the bottom and right edges are bottommost and rightmost edges of all the fragments. Directionality comes from the inline
<fantasai> see testcase on borders: https://www.software.hixie.ch/utilities/js/live-dom-viewer/?%3C!DOCTYPE%20html%3E%0A%3Cp%3EThis%20%3Cspan%20dir%3Drtl%20style%3D%22border%3A%20solid%22%3Etest%3Cbr%3Eborder%3C%2Fspan%3E%3C%2Fp%3ERESOL
<TabAtkins> emeyer, top/left/bottom edge all match Chrome's current behavior, right edge is the rightmost of *all* fragments (rather than just the first, like what FF currently does, or the last-but-clamped, like Chrome currently does)

@bfgeek
Copy link
bfgeek commented Mar 20, 2023

After thinking about this some more - the current resolution breaks a use-case. At the moment developers can use the previous behaviour to anchor something to the end fragment (if they can ensure the start of the inline always is at the start), e.g.
https://www.software.hixie.ch/utilities/js/live-dom-viewer/?saved=11438

A potential option is to alter the containing block depending on what inset properties are set instead. e.g. if both insets in an axis is set, use the bounding-rect, if one inset is set, use the first/last fragment edge.

@fantasai
Copy link
Collaborator Author
fantasai commented Mar 29, 2023

@bfgeek If you do that per-axis, then you can end up pinning things to where there isn't anything, though. E.g. if I try to position against the bottom left corner in LTR on a multiline box that doesn't start on the left edge, I'll end up somewhere at the bottom middle of the paragraph. You need to consider both axes simultaneously.

Edits for the latest WG resolution are in b961c0b btw; I'm leaving the issue open for discussion, though, and marked it in the draft.

@fantasai fantasai added the Agenda+ Later Lower-priority items that are flagged for CSSWG discussion label Mar 29, 2023
@bernhardf-ro
Copy link

A potential option is to alter the containing block depending on what inset properties are set instead. e.g. if both insets in an axis is set, use the bounding-rect, if one inset is set, use the first/last fragment edge.

This is similar to our second proposal:

To enable anchoring boxes both at the (visual) start as well as the end of the multi-line inline in all cases a conditional behavior could be introduced.
Let's say the condition is handled via a property with the values: start (initial), end and auto. auto computes to end when inset-inline-start is auto and inset-inline-end is non-auto, and to start otherwise.
The property isn't necessary as the auto behavior could always be used. But this might be to big of a change and a too far reaching effect of different inset-inline-* values.
If the condition is start the behavior is Firefox-like. If it is end it is as well, but uses the last line instead of the first, so inset-inline-end is actually relative to the visual end of the multi-line inline, like for a single line inline.
(Our approach of extending inline fragments to the line edge at their split points could be combined with this approach.)

The major difference is that we don't think there should be 3rd behavior for when both inset-inline values are non-auto. We expect using the bounding rectangle would break a lot more cases than it would improve. However, if a property to control the behavior would be introduced bounding-box could be an additional value.

if I try to position against the bottom left corner in LTR on a multiline box that doesn't start on the left edge, I'll end up somewhere at the bottom middle of the paragraph.

Considering that there are no obvious bottom-left and top-right (start-end and end-start) corners that would be fine with us. However it does show that a property giving the author various options is probably the best solution.

@tabatkins
Copy link
Member

We could consider both axises, so only if you referenced the start/start or end/end corners do we use the first/last fragment. (Or start/end in one axis with double-auto in the other axis.) If you're inconsistent, you get a fallback behavior (bounding box?).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Agenda+ Later Lower-priority items that are flagged for CSSWG discussion css-position-3 Current Work Needs Testcase (WPT)
Projects
Status: Unslotted
Development

No branches or pull requests

6 participants