w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.51k stars 671 forks source link

[css-display] How does block inside inline affect the box tree, exactly? #1477

Open Loirooriol opened 7 years ago

Loirooriol commented 7 years ago

CSS 2.1 says

When an inline box contains an in-flow block-level box, the inline box (and its inline ancestors within the same line box) are broken around the block-level box [...], splitting the inline box into two boxes (even if either side is empty), one on each side of the block-level box(es). The line boxes before the break and after the break are enclosed in anonymous block boxes

According to CSS Display Introduction, I guess now we would say the inline box is split into two box fragments.

But how does this affect the box tree? Is the parent node (in the box tree) of the block-level box still the inline box? Is the so-called "box tree" really a tree, or a more complex structure? Should we be talking about a fragment tree instead? Are box tree nodes in fact box fragments instead of boxes? Do these anonymous block boxes (the ones that enclose the line boxes before and after the break) really exist in the box tree, or do they only generate fragments in the fragment tree? Should this be explained in CSS Break?

For example, I think

<span>A<br />B<div>C</div>D</span>

generates fragments somewhat like

<fragment of="#3" first last>
  <line><fragment of="#6" first><fragment of="#1" first>A</fragment></fragment></line>
  <line><fragment of="#6" last><fragment of="#1">B</fragment></fragment></line>
</line>
</fragment>
<fragment of="#2" first last>
  <line><fragment of="#5" first last>C</fragment></line>
</fragment>
<fragment of="#4" first last>
  <line><fragment of="#7" first last><fragment of="#1" last>D</fragment></fragment></line>
</fragment>

where

But how are #1,...,#7 themselves (not their fragments) structured in the box tree?

tabatkins commented 7 years ago

This isn't a Display topic; it needs to be defined by the Flow Layout draft (currently Basic Box Model and severely unmaintained/outdated). But this is how the box tree should look:

<anon-block>
  <anon-root-inline><span-inline>A<br>B</span-inline></anon-root-inline>
</anon-block>
<div-block><anon-root-inline>C</anon-root-inline></div-block>
<anon-block>
  <anon-root-inline><span-inline>D</span-inline></anon-root-inline>
</anon-block>
tabatkins commented 7 years ago

(I've relabeled it to the currently-imaginary css-flow-1 draft, since Box isn't even in the labels system.)

Loirooriol commented 7 years ago

So, when CSS Display says

In the course of layout, boxes and text runs can be broken into multiple fragments.

it does not refer to this case? The inline box is broken into different boxes (and not fragments), just as CSS 2.1 says? I thought CSS Display Introduction replaced that part of CSS 2.1

tabatkins commented 7 years ago

No, that's just a generic statement of something that happens for a lot of reasons. For example, a block box can produce multiple fragments if broken across a multicol boundary; an inline box is broken into multiple fragments if broken across a line.

SelenIT commented 7 years ago

@tabatkins, how is this particular case different from an inline box fragmented between two line boxes belonging to different column/page/region boxes? Aren't these fragments still parts of the single inline box? If yes, why should parts of the inline box belonging to different line boxes separated by a block-level box, but otherwise behaving the same (e.g. having no padding/border at the edge where breaking occured), be treated differently?

I'm inclined to consider all these cases as similar (if not the same) cases of inline box fragmentation. Maybe they should be covered in the CSS Fragmentation spec after all? Currently that spec is a bit contradictory regarding inline box fragmentation: first it says that it doesn't cover it at all, but then it still describes the box-decoration-break behavior for inline box fragments as well. Maybe other aspects of inline box fragmentation (especially where other box types come into play) should be also covered there?

tabatkins commented 6 years ago

Agenda+ to ask a specific question. In the following scenario:

<span>inline <div>block</div> inline</span>

Is the div's block box a child of the span's inline box? (And then fragmentation causes the inline box to generate two inline fragments, one on either side of the block fragment.)

Or does block-splits-inline occur at the box-tree level, such that the span generates two inline boxes, siblings to the block box?

If the latter, which box is the span's principle box (or is it both)? This seems hard to observe, but I'm sure there are differences somewhere. More importantly, what matches browser internals most cleanly?

@fantasai and I think it's best if we go with the first option, where the span generates a single inline box (which generates two fragments), and the div generates a child block box. The other option seems... tricky.

Loirooriol commented 6 years ago

@fantasai and I think it's best if we go with the first option, where the span generates a single inline box

Yes! Move all the hand-waving into the fragment tree and let the box tree be sane. I wrote my proposal in https://github.com/w3c/csswg-drafts/issues/1706#issuecomment-323552127

dbaron commented 6 years ago

So in Gecko we don't really distinguish between boxes and fragments; they're one tree, and if we want to traverse boxes rather than fragments, we (a) ignore children that aren't first-fragments and (b) traverse children of later fragments. That said, block-in-inline and (in-progress) column-span splitting is a heavier type of splitting than breaking and bidi resolution, so there are contexts where we could mix the two modes (treat splits of inlines around blocks, and blocks and inlines around column-spans as separate objects, but not do that for bidi/line/column/page breaking), though I think we try to avoid it.

I worry that the proposal makes spec prose from CSS 2 that we still depend on even further from the truth than it currently is. In other words, we have prose that talks about boxes (at least when it remembers not to say elements) when it needs to be updated to talk about fragments -- if we change the definition of boxes to be less like fragments, future implementors may not realize that that prose was written before the distinction existed and think it means something different from what it actually means. If there were more of an ongoing effort to maintain that prose, this might be less of an issue.

That said, I'm also worried that drawing this distinction might force implementations to add new implementation concepts that they don't need now, just in order to get very minor details correct. It's often better for the spec prose to match what implementations do so that you don't get lots of very minor nonconformance. That said, I'm not sure what the other implementations do.

Loirooriol commented 6 years ago

By the way, saying that the block generates a box inside the inline one explains the propagation of text-decoration, i.e. "block" is underlined in this example:

<span style="text-decoration: underline">inline <div>block</div> inline</span>

This doesn't make sense if you only consider the fragment tree, but can make sense in the box one.

Gecko is able to do it properly despite having no box tree notion. So couldn't it use the same approach to handle the "very minor details"? I don't think implementations will be forced to add new implementation concepts.

dbaron commented 6 years ago

It can become a substantial maintenance burden; I really don't want the spec's way of explaining things to deviate from implementations if the implementations are consistent and there isn't a really good reason to go the other way.

dbaron commented 6 years ago

The mechanism that explains the propagation, by the way, is that in Gecko, the span gets split into three parts:

Having the anonymous block fragment is pretty useful for making sure pieces don't get lost when reconstructing boxes following dynamic changes to the DOM.

Though I believe text-decoration propagation in Gecko actually uses the style tree which corresponds to the element tree, not the box tree. position: relative is probably a better example.

Loirooriol commented 6 years ago

@dbaron text-decoration propagation should not happen in the element tree, see #1136 and https://bugs.chromium.org/p/chromium/issues/detail?id=553174

emilio commented 6 years ago

Yeah, pretty sure in Gecko it happens in the box tree. We rely on propagating them using a bit on the style for optimization. See https://searchfox.org/mozilla-central/search?q=symbol:_ZNK14nsStyleContext22HasTextDecorationLinesEv&redirect=false

emilio commented 6 years ago

(That is, we need to keep track of a bit in the style tree, but we compute decorations using the box tree)

upsuper commented 6 years ago

text-decoration is not actively propagated in Gecko. We collect the decorations via climbing the box tree (or frame tree in gecko's terminology) in each piece of text, as can be seen in nsTextFrame::GetTextDecorations.

The only (extra) thing we do via the style tree (element tree) is the optimization mentioned by @emilio, but that's just an optimization.

(Some off-topic discussion about Gecko's decoration line implementation.) This approach allows us to draw decoration lines as what is speced in css-text-decor-3 that each decoration line is a single line among inline boxes. However, this model may not work well with css-text-decor-4 where `text-decoration-skip` makes it possible to show decoration lines crossing objects and box decorations. We may need to rethink how that should be done for the next level when we start implementing it.
kojiishi commented 6 years ago

Blink does the latter in Tab's explanation, though it may change overtime. +cc @bfgeek

SelenIT commented 6 years ago

So it's actually two sibling inline boxes styled by default like fragments of the single inline box fragmented between two lines (and even reacting to box-decoration-break the same way)?

kojiishi commented 6 years ago

Blink tree as of today is as below, though, we probably want to revisit this at some point. And yes to @SelenIT's question, the 2 spans are like fragments.

  LayoutBlockFlow {HTML} at (0,0) size 800x96
    LayoutBlockFlow {BODY} at (8,8) size 784x80
      LayoutBlockFlow (anonymous) at (0,0) size 784x40
        LayoutInline {SPAN} at (0,0) size 11x39
          LayoutText {#text} at (0,0) size 11x19
            text run at (0,0) width 11: "A"
          LayoutBR {BR} at (11,0) size 0x19
          LayoutText {#text} at (0,20) size 10x19
            text run at (0,20) width 10: "B"
      LayoutBlockFlow (anonymous) at (0,40) size 784x20
        LayoutBlockFlow {DIV} at (0,0) size 784x20
          LayoutText {#text} at (0,0) size 11x19
            text run at (0,0) width 11: "C"
      LayoutBlockFlow (anonymous) at (0,60) size 784x20
        LayoutInline {SPAN} at (0,0) size 11x19
          LayoutText {#text} at (0,0) size 11x19
            text run at (0,0) width 11: "D"
        LayoutText {#text} at (0,0) size 0x0
css-meeting-bot commented 6 years ago

The Working Group just discussed How does block inside inline affect the box tree, exactly?, and agreed to the following resolutions:

The full IRC log of that discussion <dael> Topic: How does block inside inline affect the box tree, exactly?
<dael> github: https://github.com/w3c/csswg-drafts/issues/1477
<fantasai> Possible practical implication of previous issue might be getComputedStyle on ::marker?
<dael> TabAtkins: I recommend looking at the first comment because Loirooriol has a great markup example.
<dael> TabAtkins: Basic idea, in the box tree there's a span and a div list inside it is it an inline boc with a block box inside and during fragmentation we split that or does the splitting happen at box tree time?
<dael> florian: Observable?
<dael> TabAtkins: Effects how other properties are defined so yes in thoery. I don't think any property could be used as a probe of it. No one uses exact box tree for spec
<dael> ??: Saying splitting happens in the box tree is more consistant because operations happen in the order. If splitting is in box tree we need some kind of exception. CSS 2.1 doesn't distinguish between when it happens so it's like it happens between boxes.
<dael> TabAtkins: Impl information in FF eq they split and special case text-decoration so it propagates down.
<dael> koji: WE splits spans, wrap it in anon box, and for the box we split. That last part is same as Geck.
<astearns> s/??/Loirooriol
<fantasai> https://www.w3.org/TR/css-break-3/#break-decoration might need an update
<dael> TabAtkins: Our layout structure doesn't distinguish boxes and fragments either. Hard to argue we have one behavior or the other.
<dael> fremy: Not sure why we need to know
<dael> TabAtkins: Effects how we write specs.
<dael> iank_: Anything we do with block behavior should stay.
<dael> TabAtkins: Impl details don't matter, it's thetheoretical model. Being closer to impl matters. WE can live with either model. We just need to decide.
<dael> florian: Which makes spec writing easier?
<dael> TabAtkins: Split not until fragment time is easier I believe. Makes text-decoration easy. Other cases that treat them as a single thing work fine in that model.
<dael> dbaron: I worry a bit about making something trivial to define if it's actually more complex then impl. I think it's better if the thigns easy in impl are easy in spec and hard in impl are hard in spec.
<dael> TabAtkins: Agree.
<dael> florian: That's say split boxes not fragments.
<dael> fantasai: Shows up for box-decoration break.
<dael> ??: If splitting happens in box inline assumes if you general boxes in that case it uses the box. If one of the two inline boxes are choosen it's strange.
<astearns> s/??/Loirooriol
<dael> TabAtkins: And both are the principle box because both get all properties
<dael> fantasai: I'd prefer at fragment tree time and if we want to change the parentage maybe that's senseible. I don't think split in the box tree. And I think of this as framentation so it makes sense to say that's what's happening.
<dael> fantasai: Box decoration break should be controlling behavior at these breaks. We're slicing by default but may want to switch to clone. THis is how we define howt he side of a box that has been split are drawn and if we don't we have to define that it behaves as a fragment. If it is a fragment it' snot a special case.
<dael> florian: Split at fragment level makes a lot more sense. Seems to be a difference with impl that don't distinguish, but dbaron point earlier that this makes it easier to define hard to impl...
<dael> fantasai: Does it? There's a reason why we try to discuss and get review and it's to point out where model doesn't make sense. I see some reasons for define as fragments and only theoretical reason for the other.
<fantasai> s/reason/worries/
<dael> Rossen: The model in fragmentation spec makes a lot of sense to me. Early stages of writing draft there was back and forth for what is a fragments and what's a principle box. As an impl it was a time somewhat odd to map and refer to things not in our impl. There's not a principle box in the impl nor do we call anything a fragment, but the model matched well.
<dael> Rossen: If the model in the fragmentation spec doesn't make sense what do you propose to change?
<dael> fantasai: Didn't say it doesn't make sense? Issue is about block and inline splits. Is inline fragmented or of many boxes? A lot of impl has a structure that's not one or the other. We need the theoretical set up here.
<dael> iank_: Eventually we want to move to a model where we use fragment so I'm fine that way.
<dael> iank_: We don't want to do inline splitting at the box tree level eventually.
<dael> florian: I think so long as we have a conceptual model where boxes and fragments are different this should be at the box level. If we only want to alk baout boxes b/c matches impl that's a different conversation.
<dael> dbaron: I think there's another view to it then not having both concepts which is that you can keep boxes and fragments as terms but view the fragments as what you get when you split boxes rather then a later process stage.
<dael> florian: I't s aprocessed box?
<dael> fantasai: Isn't that what they are?
<dael> dbaron: That's what I thought until a few weeks ago. However people have said things that have made me think differently, including this issue.
<dael> florian: I don't think fragments are boxes that have bene processed in a particular way.
<dael> TabAtkins: As per spec you are right. fragment tree is the result of layout on boxes. You get a different tree out of that.
<dael> florian: Fragments is not a short term to mean a box piece. It' snot a whole buch of boxes some of which are fragments.
<dael> fantasai: A box is composed of one or more fragments.
<dael> florian: But later in the process we don't have boxes and fragments.
<dael> dbaron: You have a box tree and you go to a fragment tree by splitting things up.
<dael> dbaron: A box corrisponds to one or more fragments. If the box has children those are in one or more of the children.
<dael> fantasai: This is the case where it's not clear that's happening.
<dael> TabAtkins: In multicol even childs might fragment. putting some children in one fragment and one in the next is fair.
<dael> dbaron: If box a is parent of box b then any fragment of box b will have a parent of fragment a.
<dael> fantasai: How we lay this out they don't act like their the child of the parent. Layout wise it behaves like a sibling and lays out that way. Same is true of block. It lays out as if it's a sibling of the anon block before and after.
<dael> florian: It changes the varient but it doesn't change that you can consider the fragment tree as a processed version of the box tree. There's a little reparenting but not a lot.
<dael> Rossen: I'm trying to get to a model that makes sense. What's the model we define in the frag spec in your mind?
<dael> dbaron: I didn't see the fragmentation spec as contridicting my model.
<dael> fantasai: It doesnt' because it doesn't talk about columns.
<dael> TabAtkins: It's fragmenting easy cases.
<fantasai> s/columns/block-in-inline splits and column spanners/
<dael> Rossen: Is it easy? If you have a whole bunch of open inline elemnts, open spans, and text inside that generates some line effects and then a block element that breaks this line flow. At that moment what do you have?
<dael> dbaron: In my mental model? One of the things about the blocks inside is that some ways they act like not a child like backgrounds but for relpos they act like they are.
<dael> Rossen: If an inline element was an anchor open before and close after the block
<dael> dbaron: I think it's not dom event propagation.
<dael> Rossen: But it's type of hit test
<dael> florian: When you say some aspects siblings and some not, it's that the same as in box tree descendents and in fragments it's siblings?
<dael> dbaron: If you have it as though there's a thing there is some ways and not others it's easier to have the object there and disable features then not have it there and reconjur it when you need it.
<dael> fremy: If you have regions it's in 2 elements and your model can't have regions. They're in differnet places of the tree and they'll have fragments that aren't related.
<dael> dbaron: It was a model desc how fragmentations worked and I didn't mean you have to foloow it always. Still feels weird to me and I didn't know the spec was different until recently
<dael> florian: For Geck these are siblings or one child? The block that intrupts inlines.
<dael> dbaron: Child of the inline but it is a child of the block fragment of the inline and those are kind of special.
<dael> florian: BUt that's not a conflict. You have a single data structure that represents both box and fragment tree. In that single data structure there's a thing that keeps the parenting relationship. In box tree they're parented in fragment not. THe object is present in one of the two trees. You're just overlapping the two so it's difficult to talk abotu one concept without hte other.
<dael> Rossen: Is there a model we can agree on?
<dael> dbaron: I'm fine resolving on it. I just want to recognize it's pretty substantive decision.
<dael> Rossen: With that warning. What do people think? Ready to resolve?
<dael> TabAtkins: If people agree, yeah?
<dael> Rossen: Objections?
<dael> TabAtkins: Prop: A block inside an inline is a descendant in the box tree and a sibling in the fragment tree.
<dael> fantasai: And the inline is a single box that has been fragmented.
<dael> Rossen: You're agreeing
<dael> fantasai: They're two things.
<dael> astearns: It's not just the box that the block is a sibling.
<dael> florian: You're agreeing, there's wordsmithing.
<dael> fantasai: I want it clear that there's a single inline that has been fragmented. It's saying the block is a sibling that's a fragment which is a fragment and there's a following fragment doesn't say it's the same box.
<dael> Rossen: Prop: Blocks inside of...
<dael> Rossen: Objections to what fremy is typing?
<TabAtkins> <inline>foo<block>bar</block>baz</inline> <== 'block' is a child of 'inline' in the box tree; 'block' is a sibling of two fragments of 'inline' in the fragment tree.
<fantasai> proposed resolution: In a block-in-inline split, the block is inside the inline in the box tree, and is a sibling of the two fragments of the inline in the fragment tree
<fremy> Proposal: There is one inline box containing one block box (in the box tree). In the fragment tree, there are two inline fragments, sibling on each side of the block fragment.
<dael> Rossen: What fantasai said is the same. Objections to In a block-in-inline split, the block is inside the inline in the box tree, and is a sibling of the two fragments of the inline in the fragment tree
<dael> RESOLVED: In a block-in-inline split, the block is inside the inline in the box tree, and is a sibling of the two fragments of the inline in the fragment tree
<dael> fantasai: One more. Same applies to column spanners splitting a box. Same logic applies.
<dael> florian: Same problem but column spanner may break multi levels of parents.
<dael> Rossen: They can already have multiple.
<dael> Rossen: Agreed?
<dael> Rossen: Prop: A multicolumn spanner that splits and inline is inside of the inline box tree and a sibling to the fragment in the fragment tree
<dael> RESOLVED: A multicolumn spanner that splits and inline is inside of the inline box tree and a sibling to the fragment in the fragment tree
<dael> fantasai: We forgot anonymous boxes
<dael> <br type="lunch">
Loirooriol commented 6 years ago

I forgot that #1617 was a requirement. With the resolution above,

<main><span>A<div>B</div>C</span></main>

produces this box tree:

main: block box, IFC root
  └ span: inline box
      └ div: block box

(<main> establishes an IFC because it only contains inline-level children.)

Then the fragment tree is

fragment of main
  └ line box
      └ first fragment of span
  └ fragment of div
  └ line box
      └ last fragment of span

But this is wrong, a fragment of a box which establishes an IFC should only contain line boxes!

So we should say sequences of inline-level content directly contained in an element-generated block container are always wrapped inside an anonymous block container, which is what establishes the IFC. That is, element-generated block containers never establish an IFC.

Then the box tree is

main: block box
  └ anonymous: block box, IFC root
      └ span: inline box
          └ div: block box

and this way the fragment tree ends up as desired:

fragment of main
  └ first fragment of anonymous block
      └ line box
          └ first fragment of span
  └ fragment of div
  └ last fragment of anonymous block
      └ line box
          └ last fragment of span
MrHBS commented 1 year ago

I agree with the resolution. This issue could use the “Needs Edit” label as well.

MrHBS commented 1 year ago

So I suppose, the following markup:

<main><span>A<div>B</div>C</span></main>

According to CSS 2.1 (and css-inline-3), would produce this box tree:

main: block box
  └ anonymous: root inline box
      └ span: inline box
          └ anonymous: block box
              └ anonymous: root inline box
          └ div: block box
              └ anonymous: root inline box
          └ anonymous: block box
              └ anonymous: root inline box

With the resolution above, it would produce this box tree:

main: block box
  └ anonymous: root inline box
      └ span: inline box
          └ div: block box
              └ anonymous: root inline box

Is this correct?

Loirooriol commented 1 year ago

The 2nd part seems correct. But CSS 2 didn't have root inline boxes, and inline boxes couldn't contain block-level boxes.