Skip to content

[css-sizing-4] How does height: stretch interact with margin collapsing with parent #11044

Closed

Description

@dbaron

There were some possibly-related previous discussions in #923 and #1614.

The definition of stretch-fit sizing says:

Additionally, in formatting contexts and axes in which the relevant self-alignment property does not apply (such as the block axis in Block Layout, or the main axis in Flex Layout), in cases where a percentage size in that axis would resolve to a definite value, a stretch-fit size causes the box to attempt to fill its containing block—​behaving as 100% but applying the resulting size to its margin box instead of the box indicated by box-sizing. For this purpose, auto margins are treated as zero, and furthermore, for block-level boxes in particular, if its block-start/block-end margin would be adjoining to its parent’s block-start/block-end margin if its parent’s sizing properties all had their initial values, then its block-start/block-end margin is treated as zero.

It's not clear to me if this last sentence that says the margin is treated as zero is also intended to imply that there are revisions to the margin collapsing rules that make the margin show up on the parent. (I don't know of any version of the margin collapsing rules other than the one in CSS 2.1.) At first glance it seems like this might make sense. However, it doesn't make much sense in the case where this element has siblings. In particular:

  • The sizing rule given here sizes the element as though it has no siblings. But it might, in fact, have siblings. (But making stretch behave "correctly" when there are siblings might be something we don't really want to do -- it's certainly difficult, particularly when more than one element has stretch.)
  • The current spec definition of margin-collapsing is intentionally asymmetric: blocks with a non-auto height do not collapse their end margin with their last child. If this is intended to change that in some cases, it should probably be more explicit about it. This spec text introduces a hypothetical "if its parent's sizing properties all had their initial values" to explicitly avoid that rule, but as a result it makes a margin disappear that doesn't end up anywhere else.
  • If the goal were to have margins collapse if the stretch sizes the children to exactly fit the parent, it seems like the special collapsing rules should only apply when there aren't siblings to mess up the sizing, or something like that.

It's not clear to me why it's desirable to make these margins disappear given the current rules for handling siblings and margin collapsing. It seems like a straightforward fix would be to remove the special rule about zeroing margins for block-level boxes. Why don't we want to just allow the normal rules to run and size the element using its margins?

Or was there an intention that siblings and/or margin collapsing be handled differently?

Activity

  1. Loirooriol commented on Oct 17, 2024

    @Loirooriol
    Contributor

    Quick test: https://software.hixie.ch/utilities/js/live-dom-viewer/saved/13208

    <!DOCTYPE html>
    <style>.stretch { height: -moz-available; height: -webkit-fill-available; height: stretch }</style>
    <div style="outline: solid; height: 100px; width: 100px">
      <div class="stretch" style="margin: 10px; border: solid magenta"></div>
    </div>
    <br>
    <div style="outline: solid; height: 100px; width: 100px">
      <div style="overflow: hidden"></div>
      <div class="stretch" style="margin: 10px; border: solid cyan"></div>
      <div style="overflow: hidden"></div>
    </div>
    Gecko Blink WebKit

    I think the spec says that magenta should be like WebKit, and cyan like Blink, which is what looks the best to me.

  2. dbaron commented on Oct 17, 2024

    @dbaron
    MemberAuthor

    It's not completely clear to me why we want to ignore the margin on the magenta test, though. Is it really correct that the margin should just disappear? Or should we at least expect the top margin to get collapsed to the outside of the parent? (And does it then make sense to ignore the bottom margin to get some symmetry -- but not full symmetry -- since we don't want to change the margin collapsing rules too much?)

  3. Loirooriol commented on Oct 17, 2024

    @Loirooriol
    Contributor

    Yes, the top margin collapses as normal, the bottom one doesn't (because of height: 100px) but it looks like collapsing.

  4. Loirooriol commented on Oct 17, 2024

    @Loirooriol
    Contributor

    Maybe it's clearer like

    <!DOCTYPE html>
    <style>.stretch { height: -moz-available; height: -webkit-fill-available; height: stretch }</style>
    ⬇️ margin here
    <div style="outline: solid; height: 100px; width: 100px">
      <div class="stretch" style="margin: 30px; border: solid magenta"></div>
    </div>
    ⬆️ no margin here

    The bottom margin should just be considered as collapsing with the parent for the purpose of resolving stretch. But then it doesn't actually collapse, it just overflows.

  5. dbaron commented on Oct 17, 2024

    @dbaron
    MemberAuthor

    I guess I thought the margin gets treated as zero, and doesn't get treated as collapsing. But now I think actually the spec is unclear, because it's not clear if the "and furthermore" part of the sentence is still within the scope of the "For this purpose" earlier in the sentence, or if the "and" escapes that scope. That is, it's not clear if the sentence is structured as "(For this purpose, X), and furthermore, Y." or "For this purpose, (X, and furthermore, Y).".

    (I should also mention that this issue came out of a code review discussion.)

  6. Loirooriol commented on Oct 17, 2024

    @Loirooriol
    Contributor

    I think it's the latter, the former seems weird.

  7. bfgeek commented on Oct 17, 2024

    @bfgeek

    Some practical constraints from an engine perspective.
    Blink doesn't know anything about any proceeding margins, e.g.

    <div style="height: stretch; margin-bottom: 10px"></div>
    <div style="margin-bottom: 20px;"></div>
    content
    

    (Blink at the time of giving the first div a height, doesn't know about the 20px margin).
    This is a simple case, but the end margins can come from an arbitrary sibling, and we really don't want to break this invariant.

    From a simplicity of implementation POV ignoring margins entirely might be an option.

    A tradeoff which might be acceptable is to consider the elements (own) margins if the element is a direct child of a BFC. But from an implementation simplicity POV ignore collapsing e.g.

    <div style="display: root; height: 100px;">
      <div style="margin: 10px; height: stretch;"></div>
    </div>
    

    Then for the case where the child has a non-BFC parent ignore the margins.

    Also note that we are somewhat compat constrained if we want to keep stretch as a pure alias of -webkit-fill-available.

    Ian

  8. Loirooriol commented on Oct 18, 2024

    @Loirooriol
    Contributor

    we are somewhat compat constrained if we want to keep stretch as a pure alias of -webkit-fill-available

    Blink and WebKit have different behaviors, so it's probably fine if we choose one or the other depending on the case.

    Blink doesn't know anything about any proceeding margins

    In Servo we parallelize block layout (when there are no floats), so we also have this problem for preceding margins.

    ignoring margins entirely might be an option

    Do you mean to just stretch the border box like WebKit? This can result in overflow if the margins don't end up collapsing, which seems bad.

    consider the elements (own) margins if the element is a direct child of a BFC

    I think that ignoring margins for non-BFC parent is too restrictive.

    So I have another proposal. Given that:

    • stretch doesn't make that much sense if there are siblings
    • Margins collapsing through boxes was a mistake and "the root of all margin-collapsing evil".
    • In case of doubt, it seems better to not overflow

    Then we could, for the purpose of resolving a vertical stretch on a block-level box:

    • Treat the top margin as 0 if the box has no preceding sibling, and the top margin is adjoining with the top margin of its parent
    • Treat the bottom margin as 0 if the box has no following sibling, and the bottom margin would be adjoining with the bottom margin of its parent if the parent’s sizing properties all had their initial values.

    For the purpose of checking preceding/following siblings, ignore collapsed whitespace and out-of-flow elements.

    So then I think we only need to check:

    • Whether the parent establishes an independent BFC
    • Whether the parent has vertical borders or padding
    • Whether our element has clearance (only for the top margin)
    • Whether our element is the first/last child

    Seems fine from an implementation POV.

  9. 56 remaining items

  10. Loirooriol commented on Mar 11, 2025

    @Loirooriol
    Contributor

    The spec needs to consider floats, filed #11905

  11. added a commit that references this issue on Mar 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions