Skip to content

Please define how the various box tree fixups interact #1355

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

Closed
bzbarsky opened this issue May 12, 2017 · 15 comments
Closed

Please define how the various box tree fixups interact #1355

bzbarsky opened this issue May 12, 2017 · 15 comments

Comments

@bzbarsky
Copy link

There are now multiple CSS specs that define various computed display and box tree fixups. For example (this is probably not an exhaustive list):

The ordering of this stuff needs to be defined. And yes, it matters, apparently. Some examples of it mattering:

  1. Gecko code has comments about how the ruby bits have to come after the grid bits, for certain tests to pass.
  2. If you have a block-inside-inline all inside a ruby container, what happens? The ruby things say they happen "after any intermediary anonymous-box fixup", but it's not clear to me whether the anonymous-block thing above counts as "intermediary anonymous-box fixup". If it does, then it seems like the output should be three inline-block boxes, the first containing the first inline, the second being the inlinized block, and the third containing the third inline. If it doesn't, then the output should be a ruby container with a block child.

For the second item, I tested what some UAs do. Testcase:

  <ruby>
    Start
    <span style="border: 10px solid green">
      <div style="background: yellow">Text</div>
    </span>
    End
  </ruby>

Observed behavior: Chrome, Safari, and Edge don't inlinize as far as I can tell, no matter whether the <span> is there. Firefox does inlinize, but does that in some sort of deep traversal and before doing the block-inside-inline fixup, so ends up with a green box around "Text".

@upsuper
Copy link
Member

upsuper commented May 12, 2017

(cc myself)

@Loirooriol
Copy link
Contributor

From CSS display, the point in inlinifications being recursive is

If a box with a flow inner display type is inlinified, it recursively inlinifies all of its in-flow children, so that no block-level descendants break up the inline formatting context in which it participates.

Presumably, this implies blocks breaking inline parents happens after inlinification. Otherwise they would have already broken the inline formatting context.

@bzbarsky
Copy link
Author

My point is that all this needs to be clearly defined in the relevant specs, probably by all the fixups being driven from a single spec that specifies the ordering, instead of implementors needing to guess and read tea leaves and sacrifice chickens to divine intentions.

@tabatkins tabatkins added the css-display-3 Current Work label May 16, 2017
@tabatkins
Copy link
Member

@fantasai and I think the ordering ends up being real simple:

  1. Computed-value fixups, such as from position, float, or mismatched-writing-mode, and all other blockification/inlinification fixups. None of these conflict: position and float both blockify, and take it out-of-flow so the others don't do anything; writing-mode changes the inner display type, block/inlinification changes the outer display type (and block/inline never both occur on a given element).
  2. Anonymous box fixup: table, ruby, block-in-inline. These are mutually exclusive, because they occur in different formatting contexts. Anonymous box generation happens after computed value fixup because box generation in general happens after computed values are completely resolved as defined in https://drafts.csswg.org/css-display/#intro (because we need the computed values in order to know what boxes to generate).

Wrt to your Ruby example, only FF does it correctly - the inlinification rules were added to ruby to avoid the complication of doing block-in-inline fixup.

Is this a sufficient categorization or do you need something actually added to the spec here (if so, what)?

@bzbarsky
Copy link
Author

None of these conflict

The code comments suggesting that the computed-value fixups from ruby and grid have to happen in a particular order don't make me very confident in this claim, unfortunately. It's possible those comments are wrong, of course.

These are mutually exclusive, because they occur in different formatting contexts

Really? What happens when I have a ruby-text-container parent and a table-cell child? Which anonymous box fixups apply and in what order and why?

Anonymous box generation happens after computed value fixup

That's ... not what the ruby draft I linked above says, just to be clear. It explicitly says that ruby-triggered inlinization happens after anonymous box generation for tables, and that it affects computed display values.

because box generation in general happens after computed values are completely resolved

Yes, that would be the sane thing. The specs explicitly say that's not how it works, at the moment.

or do you need something actually added to the spec here

Well, it would be good if the categorization given didn't disagree with existing spec text, for a start. That probably involves changing said spec text.

Just to make my position crystal clear:

  1. The current situation is complicated enough that neither implementors nor spec editors know what's going on, as far as I can tell.
  2. That lack of understanding and clarity about what's going on means it's easy for people to write specs and create implementations that make the whole thing fall down. On the spec side, that would include explicit wording that violates the ordering of your items 1 and 2, computed value fixups that conflict with other computed value fixups (that's assuming we have no such conflicts so far; even if we assume that, it's far too easy to accidentally create one), anonymous box fixups that conflict with each other, and anonymous box fixups that do not make sense (e.g. in my ruby-text-container+table-cell example above, do we get an anonymous inline-table or an anonymous table? CSS2.1 says "table for everything that's not display:inline", but I strongly suspect it should be an inline-table for general sanity).

I would like to avoid the problems in my item 2. I think addressing item 1 is likely the best way to get there. My specific suggestion is to make this stuff very explicit: if anonymous box fixups happen after all computed style fixups, they should clearly say so (and neither one say the opposite).

So a possible start would in fact be to have the two sets of fixups, have all fixups say which set they're in, explicitly invoke the computed-value fixups at cascade time, explicitly invoke the box tree fixups from box tree construction.

Or put another way, whenever someone adds a new fixup, it should be easy for them to check whether it conflicts with existing fixups. Right now that is more or less rocket science. Even getting a list of existing fixups is rocket science.

@tabatkins
Copy link
Member

The code comments suggesting that the computed-value fixups from ruby and grid have to happen in a particular order don't make me very confident in this claim, unfortunately. It's possible those comments are wrong, of course.

Those are in two different categories - ruby fixup (I presume you mean wrapping naked ruby-internal display types) is #2, while Grid blockification is #1. I agree that, previously, it wasn't completely clear that the two categories were strictly divided as they are; that's why we had to add text to Flexbox for it (because we actually discussed it and made an ordering choice, which luckily accorded with the most reasonable-in-hindsight ordering).

Really? What happens when I have a ruby-text-container parent and a table-cell child? Which anonymous box fixups apply and in what order and why?

ruby-text-container inlinifies its contents - that's a #1 fixup of computed values. This happens before table-cell tries to wrap itself in anonymous boxes (and in fact prevents that from happening, because it turns into an inline).

That's ... not what the ruby draft I linked above says, just to be clear. It explicitly says that ruby-triggered inlinization happens after anonymous box generation for tables, and that it affects computed display values.

Yup. :( The Ruby draft text is old and predates Display, Grid, and other things that made the ordering much clearer. It needs to be updated. (#1341 on ruby inlinification covers this issue.)

Yes, that would be the sane thing. The specs explicitly say that's not how it works, at the moment.

Yeah, just the Ruby spec afaik. #1341

My specific suggestion is to make this stuff very explicit: if anonymous box fixups happen after all computed style fixups, they should clearly say so (and neither one say the opposite).

Afaict, this is precisely what https://drafts.csswg.org/css-display/#intro says (which is normative) - it gives a precise ordering to things, with computed-values happening before box-generation (and anonymous box generation specifically). Anything missing from there?

@bzbarsky
Copy link
Author

I presume you mean wrapping naked ruby-internal display types

No, I mean ruby-triggered inlinization. Gecko does in fact do all the computed display bits first, before we start doing anything with boxes, because sanity.

and in fact prevents that from happening, because it turns into an inline

That's not what https://drafts.csswg.org/css-ruby/#anon-gen-inlinize says to do (only block-level things are inlinized, and table-cell is not block-level), and not what Firefox does. Simple testcase (using table-row instead of table-cell because it's clearer to eyeball):

<span style="display: ruby-text-container">
  <span style="display: table-row">First</span>
  <span style="display: table-row">Second</span>
</span>

that generates an anonymous inline-table around the two rows in Firefox, I'm pretty sure. But that's not defined sanely in any spec, because CSS anon box creation doesn't know about new display types either....

The Ruby draft text is old

It claims to be dated today. ;) Anyway, clearly changes to the ruby draft are needed; we all agree on that, which is good.

this is precisely what https://drafts.csswg.org/css-display/#intro says

That's pretty helpful, yes. It would be even better if all fixup definitions linked to it so you could go from a thing you're looking at to the thing that defines how the thing you're looking at fits into the world.

And again, adding a fixup should be a sane process, which means being able to easily determine what other fixups it needs to interact with.

fantasai added a commit that referenced this issue May 16, 2017
…n with how Flex and Grid handle their contents. #1355
@tabatkins
Copy link
Member

No, I mean ruby-triggered inlinization. Gecko does in fact do all the computed display bits first, before we start doing anything with boxes, because sanity.

Then I'm not sure what you're referring to; Grid blockification and Ruby inlinification can't happen to the same elements.

It claims to be dated today. ;) Anyway, clearly changes to the ruby draft are needed; we all agree on that, which is good.

The spec generator runs multiple times a day, and the CI we're using doesn't automatically inform the spec of when it was last meaningfully touched, so all the Bikeshedded drafts claim to be updated today. ^_^ (I'll bug plinss about this - I think we can do something about that.)

@fantasai and I just reviewed the Ruby spec real quick; at minimum, steps 1 and 2 of the fixup algo need to be swapped, which she's doing right now. We'll actually fix the Step 2 text (which is inconsistent with Display's model, and how Flexbox/Grid now work) in a separate issue.

That's pretty helpful, yes. It would be even better if all fixup definitions linked to it so you could go from a thing you're looking at to the thing that defines how the thing you're looking at fits into the world.

Hmm. @fantasai and I will have to think a bit on how to expose an anchor reasonably in a way that reads well when used.

And again, adding a fixup should be a sane process, which means being able to easily determine what other fixups it needs to interact with.

A given fixup is either adjusting computed styles or adding anonymous boxes, which determines where it goes automatically. And as I outlined before, I think all the fixups in each category are either compatible or mutually exclusive, so they don't need an explicit ordering yet. Right?

@upsuper
Copy link
Member

upsuper commented May 16, 2017

I think one thing which wasn't clear is what should happen when inlinification or blockification is applied to a layout-internal display type. That happens when, e.g. a ruby internal inside a grid/flex, or a table internal inside a ruby.

I just checked CSS Display again, and it seems that it has defined it clearly that, when those algorithms are applied to a layout-internal display type, they would just be converted to inline and block respectively.

This isn't what Firefox currently does for ruby. We are currently trying to do smart things like avoiding doing inlinification for non-block level display type, and instead just hoping anonymous box generation would fix up everything. This is probably fragile. I think we can change it to do what the spec says about inlinification.

@bzbarsky
Copy link
Author

Then I'm not sure what you're referring to

I think the relevant situation is this:

<span style="display: grid"><span style="display: ruby-text-container"><div></div></span></span>

What ends up happening with the display of the <div>?

A given fixup is either adjusting computed styles or adding anonymous boxes, which determines where it goes automatically.

Let's posit that.

I think all the fixups in each category are either compatible or mutually exclusive

I think we're talking past each other. I'm explicitly talking through what has to happen when someone adds a new fixup. How do they make sure it's mutually exclusive or compatible with all existing ones?

But even past that, it's not clear to me that what you said is true, because nothing defines when these fixups take place. In my example above with grid and ruby-text-container, does the fixup, if any, for the <div> take place before or after fixup, if any, for the inner <span>? Where is this ordering defined? Are all fixups applied to a given element before any fixups are applied to its descendants, or is each fixup performed as a separate pass over the tree?

Seriously, this stuff needs to actually be defined. What we have now is a non-interoperable mess that's not possible to implement interoperably based on what the specs actually say. :(

@Loirooriol
Copy link
Contributor

Loirooriol commented May 17, 2017

After the edit in CSS ruby it's a bit more clear, but the ordering in #1355 (comment) should probably be explicitly explained in the Introduction of CSS Display.

However, I think it's not that simple. For example,

position and float both blockify, and take it out-of-flow so the others don't do anything

This is not what happens in a flex or grid context, because floating does not apply to flex or grid items, so "others" (the blockification induced by the formatting context) do "something". But floating applies in a ruby context. So I believe Tab's step number 1 in fact is something like the following:

  1. Import the root element.

To import an element (or pseudo-element) E into an optional box P in an optional context F,

  1. If P or F have not been passed,
    • Assert: E is the root element.
    • display: contents is treated as block for E.
  2. If display has the value none, or if E is a pseudo-element with content: none,
    • Positioning and floating do not apply.
    • E generates no box.
    • End this algorithm.
  3. If display has the value contents,
    • Positioning and floating do not apply.
    • E generates no box.
    • Import the contents of E into the box P in the context F.
    • End this algorithm.
  4. E generates a box B according to the display value, which is assigned the same styles as E.
  5. Let in-flow be a boolean flag.
  6. If the E is the root element,
    • Not sure if it has already been decided whether position may be treated as static and float may be treated as none or not. Handle appropriately.
  7. Otherwise, absolutely positioning applies if specified. In that case,
    • The box B is blockified.
    • Floating does not apply.
    • Set in-flow flag to false.
  8. Otherwise, if F blockifies,
    • The box B is blockified.
    • Floating does not apply.
    • Set in-flow flag to true.
  9. Otherwise, floating applies if specified. In that case,
    • The box B is blockified.
    • Set in-flow flag to false.
  10. Otherwise, if F inlinifies,
    • The box B is inlinified.
    • Set in-flow flag to true.
  11. Otherwise,
    • Set in-flow flag to true.
  12. If P and F have been passed,
    1. If box B has a flow inner display type and has a different writing-mode than box P,
      • The inner display type of box B computes to flow-root.
    2. Tentatively insert B into P with in-flow flag using F's algorithm.
  13. Otherwise,
    • The box B is blockified.
    • The box B becomes the root box.
  14. Possibly let B establish a new formatting context according to its inner display type and other properties.
  15. Set F to the formatting context established by the nearest inclusive ancestor box of B.
  16. If E's display has the list-item keyword,
    • Import E's ::marker pseudo-element into box B in the context F.
  17. Import the contents of E into the box B in the context F.
  18. Close box B using F's algorithm.

To import the contents of an element E into a box B in a context F, do

  1. Import E's ::before pseudo-element into box B in the context F.
  2. For each child node C of E,
    1. If C is an element node,
      1. Import element C into box B in the context F.
    2. Otherwise, if C is a text node,
      1. Consume following text node siblings.
      2. This sequence of text nodes generates a text run T, which is assigned the same styles as the generating text nodes.
      3. Tentatively insert the text run T into B using F's algorithm.
  3. Import E's ::after pseudo-element into box B in the context F.

To import the contents of a pseudo-element E into a box B in a context F, do

  1. Treat E as if it contained a sequence of text nodes, according to the content property.
  2. This sequence of text nodes generates a text run T, which is assigned the same styles as the generating text nodes.
  3. Tentatively insert the text run T into B using F's algorithm.

Each formatting context should define (this is Tab's step 2):

  • When a new box is tentatively inserted inside another box in that formatting context, whether anonymous boxes should be inserted in between, or the inner one should break the outer one, etc. This might depend on the display types of these two boxes and on the in-flow flag.
  • When a text run is tentatively inserted inside another box in that formatting context, whether anonymous boxes should be inserted in between, or the text run should be discarded, etc. This might depend on the display types of that box.
  • When a box is "closed" in that formatting context, whether anonymous boxes should be inserted inside, or the box should be discarded, etc. This might depend on the display types of that box.
  • Additional hooks may be needed.

But what if some of these anonymous boxes inserted in-between do inlinify or blockify their contents, or do something which is supposed to be handled in Tab's step 1? I don't think this is currently possible, but as Boris says, adding a new fixup could mess this up.

Anyways, in the algorithm above I'm sure I missed lots of details. I would love someone to do it properly and include it in some spec.

@fantasai
Copy link
Collaborator

Filed @upsuper's question about inlinification of layout-internal types as #1390.

Wrt @bzbarsky's comment #1355 (comment)

<span style="display: grid"><span style="display: ruby-text-container"><div></div></span></span>

According to CSS Display,

To create the box tree, CSS first uses cascading and inheritance, to assign a computed value for each CSS property to each element in the source tree. (See [CSS3-CASCADE].)
Then, for each element, it generates zero or more boxes as specified by that element’s display property.

The question in @bzbarsky's example is that, if we resolve ruby-text-container's computed value first, then it becomes block and the inner div remains a block as well. If we resolve the inner div's computed value first, then it becomes an inline-block.

A general principle of CSS is that constraints, insofar as we can make them so, are insensitive to a timeline and described as a steady state, and appending elements to the tree shouldn't have unexpected implicit side-effects on earlier elements. If we convert the innermost div to inline-block because it is inside a ruby-text-container, and then convert the ruby-text-container to a block, then we notice that the inner div has lost its reason for being an inline-block. This isn't how we want things to work: starting with a tree without the inner div, and then inserting it later, it would be a block not an inline-block.

Furthermore, inheritance and style computation works from the top down. We compute against the computed value of a parent's display, not its specified or cascaded value. And if we're looking at the box tree, certainly the boxes so generated are done based on the computed value of display, not its specified or cascaded value, because the computed value is what determines the type of box to generate. So I think we can safely say that the intent is for this example to result in a grid containing a block containing a block.

In my example above with grid and ruby-text-container, does the fixup, if any, for the

take place before or after fixup, if any, for the inner ? Where is this ordering defined? Are all fixups applied to a given element before any fixups are applied to its descendants, or is each fixup performed as a separate pass over the tree?

Computed value fixups that reference either a parent's display value or its box type by definition reference its computed display value (which is final), not some hypothetical in-between state. This requires that all fixups are applied to a parent before its children are evaluated.

@tabatkins
Copy link
Member

So I think we can safely say that the intent is for this example to result in a grid containing a block containing a block.

Correct. Your point on computation moving down the tree is correct - you can't compute the inner div's display until you know the span's computed display, and you can't compute that until you know the outer span's computed display. The outermost one is well-specified - it's a grid - so that dictates the inner span becomes blockified into a block flow, which dictates that the inner div is fine and can stay its default block.

Nothing implicit here or relying on appeals to general timelessness principles, it's just the way data naturally flows in the constraints CSS imposes.

@bzbarsky

I'm explicitly talking through what has to happen when someone adds a new fixup. How do they make sure it's mutually exclusive or compatible with all existing ones?

Yeah, there currently isn't a way. This could definitely benefit from, as you say, an easy link that we consistently use into the Display intro, and probably a coordinating wiki page like we do for other distributed-consistency problems in CSS.

triple-underscore added a commit to triple-underscore/triple-underscore.github.io that referenced this issue Jul 6, 2017
Commits on Jul 6, 2017

Note that 'display: contents' does not affect property inheritance.
Fixes w3c/csswg-drafts#1473
w3c/csswg-drafts@356b3296d5b20e00bedbfa8d1db59
d005a73bfc9

Normatively define what was in the previous note: that run-in fixup
occurs before first line determination.
w3c/csswg-drafts@37c01a4ef94aec7c352c3dc7c5cfc
31f8898ed4d

Move computed value bullet before box generation bullet.
w3c/csswg-drafts@5d10db8d8665f52994530304f2ddc
88236d37ccb

Clarify that run-in fixup occurs before block/inline fixup.
w3c/csswg-drafts@6f6913e20851f50ad81ca3cb43a49
9c01e8b2186

Expand out informal definitions of BFC.
Also fixes w3c/csswg-drafts#1471 .
w3c/csswg-drafts@388b1fb3bfa7dbe2ed112581322d2
a10787c9121

Refer to 'flow layout' directly, rather than just mentioning the types
of boxes that flow layout can produce.
Fixes w3c/csswg-drafts#1470 .
w3c/csswg-drafts@09212af80e10e4e3e57965bb27b2d
e3fdcd86e06

Make the run-in recursion explicit, so it's clear the second step
doesn't recurse.
Fixes w3c/csswg-drafts#1460 .
w3c/csswg-drafts@c9f1bb16d0e5077b9bca7027a7d72
52eeff5abf1

Be a little looser about what counts as abspos, because css-lists
defines as out-of-flow.
Fixes w3c/csswg-drafts#1458
w3c/csswg-drafts@f7ff60660c3b5e1b31000ffa0f729
ba04f13a2f8

Remove the w3c/csswg-drafts#1390 inline issue.
w3c/csswg-drafts@4514b3e7f2f0aa8ef95a84c107fb9
90fc2def209

Add note about the two categories of box-tree fixup, and their relative
ordering.
Fixes w3c/csswg-drafts#1355 .
w3c/csswg-drafts@f5b79e99bb8fc9ebea4ed956ed342
0bc69b90786

Apply diff, per resolution.
Fixes w3c/csswg-drafts#1118 .
w3c/csswg-drafts@dbb7042481d132efcb6869ecbdf44
f338d8ad944
fantasai added a commit that referenced this issue Mar 20, 2018
@fantasai
Copy link
Collaborator

Since inlinification doesn't affect layout-internal display types (expecting such boxes to be wrapped in an appropriate anonymous inline-level wrapper by the relevant box-fixup rules), I also added ec80a76 to address misparented internal table boxes inside ruby. (We were assuming they'd be considered inline-level due to fixup, but it wasn't really clear since if table box fixup hasn't run yet, they aren't yet wrapped.)

CSS Tables L3 already recognizes ruby in determining anonymous table box types, so I don't think there are any issues. there.

@fantasai
Copy link
Collaborator

Idk what to close this as, the dependencies are all different... >_<;;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants