Closed Bug 1322191 Opened 8 years ago Closed 7 years ago

Implement CSS `display: flow-root` (modern clearfix)

Categories

(Core :: CSS Parsing and Computation, enhancement, P4)

enhancement

Tracking

()

RESOLVED FIXED
mozilla53
Tracking Status
firefox53 --- fixed

People

(Reporter: mtanalin, Assigned: MatsPalmgren_bugz)

References

()

Details

(Keywords: dev-doc-complete)

Attachments

(5 files, 1 obsolete file)

User Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0
Build ID: 20161205004004

Steps to reproduce:

`display: flow-root` is a modern way to force a block to be a formatting context that floated elements are contained in (aka clearfix).

From the spec [1]:

> The element generates a block container box, and lays out its contents using flow layout. It always establishes a new block formatting context for its contents.

Tab Atkins and Elika Etemad (fantasai) from CSSWG consider the feature stable enough to be implemented [2].

[1] https://drafts.csswg.org/css-display-3/#valdef-display-flow-root
[2] https://discourse.wicg.io/t/1835/6
Component: Untriaged → Layout
Product: Firefox → Core
Version: 52 Branch → unspecified
Assignee: nobody → mats
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true
OS: Unspecified → All
Priority: -- → P4
Hardware: Unspecified → All
Severity: normal → enhancement
Component: Layout → CSS Parsing and Computation
This feature is trivial so a pref seems like overkill, but I guess
it doesn't hurt to have just in case...
Attachment #8820788 - Flags: review?(dholbert)
Could you send an intent to implement and ship to dev-platform, per https://wiki.mozilla.org/WebAPI/ExposureGuidelines
Flags: needinfo?(mats)
Fwiw, here is the corresponding feature request for Edge (Bugzilla doesn’t allow to add it to the “See Also” field):

https://wpdev.uservoice.com/forums/257854/suggestions/17420707
Comment on attachment 8820791 [details] [diff] [review]
part 4 - Regenerate devtools properties-db.js after adding display:flow-root.

Review of attachment 8820791 [details] [diff] [review]:
-----------------------------------------------------------------

Reviewing part 4, the most trivial part, first. Just one observation/question (which you might not have an answer to) -- r=me regardless.

::: devtools/shared/css/generated/properties-db.js
@@ -3259,4 @@
>        "forwards",
>        "full-width",
>        "geometricprecision",
> -      "grayscale",

Do you know what's up with this "grayscale" removal? That value looks wholly unrelated to this patch. I wonder why it's in there (and why the autogeneration tool decided that now is a good time to remove it).  At first I thought maybe this was a value that was recently-unsupported, but I don't see any recent csets that mention "grayscale" in their diff.  Maybe it's a platform-specific value (e.g. only meaningful on mac) and it's not supported on your platform?

Anyway, if we had a better process for maintaining this autogenerated file, I'd be more concerned with auditing this change & perhaps splitting this into its own cleanup patch. But we don't have a great story for this file right now... so, *shrug* meh.
Attachment #8820791 - Flags: review?(dholbert) → review+
(You might consider explicitly mentioning the command that you used to regenerate the properties-db.js file, in the extended commit message of part 4.  It's always nice to provide the command that was used, in autogenerated patches, for auditing/posterity/people-who-need-to-generate-similar-patches-and-are-cribbing-from-yours.)
Comment on attachment 8820788 [details] [diff] [review]
part 1 - [css-display] Add style system support for display:flow-root.  Enable it by default.

Review of attachment 8820788 [details] [diff] [review]:
-----------------------------------------------------------------

r=me with nits addressed as you see fit

::: layout/base/nsLayoutUtils.cpp
@@ +326,5 @@
> +  bool isDisplayFlowRootEnabled =
> +    Preferences::GetBool(DISPLAY_FLOW_ROOT_ENABLED_PREF_NAME, false);
> +
> +  if (!sIsDisplayFlowRootKeywordIndexInitialized) {
> +    // First run: find the position of "flow_root" in kDisplayKTable.

"flow_root" is not a thing. I think this means to say "flow-root"? (with a hyphen)

(I'm guessing this is just a search-and-replace error, when fixing up eCSSKeyword_*** naming).

@@ +333,5 @@
> +                                     nsCSSProps::kDisplayKTable);
> +    sIsDisplayFlowRootKeywordIndexInitialized = true;
> +  }
> +
> +  // OK -- now, stomp on or restore the "flow_root" entry in kDisplayKTable,

Same here -- hyphenate "flow-root" please.

::: layout/style/nsStyleStruct.h
@@ +2885,5 @@
>      return mozilla::StyleDisplay::Block == mDisplay ||
>             mozilla::StyleDisplay::ListItem == mDisplay ||
>             mozilla::StyleDisplay::InlineBlock == mDisplay ||
> +           mozilla::StyleDisplay::TableCaption == mDisplay ||
> +           mozilla::StyleDisplay::FlowRoot == mDisplay;

Since "FlowRoot" is so closely tied to "Block", maybe it'd be best to insert it *directly* after "Block" in lists such as this one?  (rather than just inserting at the end of the list, as this patch seems to generally do right now)

(The bulk of this patch is insertion into such lists.)
Attachment #8820788 - Flags: review?(dholbert) → review+
Comment on attachment 8820789 [details] [diff] [review]
part 2 - [css-display] Implement layout for display:flow-root.

Review of attachment 8820789 [details] [diff] [review]:
-----------------------------------------------------------------

::: layout/generic/nsBlockFrame.cpp
@@ +300,5 @@
>  {
> +  auto blockFrame = new (aPresShell) nsBlockFrame(aContext);
> +  if (aContext->StyleDisplay()->mDisplay == StyleDisplay::FlowRoot) {
> +    blockFrame->AddStateBits(NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT);
> +  }

Looks like this new special-case (in NS_NewBlockFrame) matches what we already do in NS_NewBlockFormattingContext.  (And NS_NewBlockFormattingContext itself does also call into NS_NewBlockFrame -- so it may end up setting these bits twice now, which is probably cheap albeit a little messy/redundant.)

Would it be possible/cleaner to just wire up "display:flow-root" to invoke NS_NewBlockFormattingContext directly?  That would make the connection between the spec & the code a bit clearer, I think.  (In isolation, it's unclear why these specific bits are set here. But NS_NewBlockFormattingContext is more self-documenting [via its name] about what this constellation of bits is associated with.
(In reply to Daniel Holbert [:dholbert] from comment #8)
Dunno, it's just the result that "./mach devtools-css-db" gave me.
It appears to be a value associated with 'font-smoothing' and 'filter'?
I have no clue why the tool decided to remove it.  I'll leave it in since
it looks like it's still a valid value.  It's hard to know because people
often forget to run that command leaving the next guy to pick up the changes.

Anyway, filed bug 1325472.  And I've already complained that this stuff
should be automated so humans don't have to deal with it, because... mistakes,
in bug 1320607, but apparently it's too complicated :-(
Comment on attachment 8820790 [details] [diff] [review]
part 3 - [css-display] Tests for display:flow-root.

Review of attachment 8820790 [details] [diff] [review]:
-----------------------------------------------------------------

r=me on part 3, with nits addressed as you see fit:

::: layout/reftests/css-display/display-flow-root-001.html
@@ +26,5 @@
> +<body>
> +
> +<div style="border:1px solid">
> +  <span style="display:flow-root; margin: 20px 0">
> +    <div style="margin: 20px 0">x</div>

If you could, it'd be helpful to add some brief HTML comments to hint at what you're testing in each section of this test.  (In particular: flow-root affects floats & margins, and this test has 2 margin-only chunks, 1 float-only chunk, and 1 float+margins chunk.  And it's not superficially clear what expectation each of these different chunks is aiming to test, & how they differ from one another.)

(e.g. I think this part is testing that vertical margins outside the flow-root don't collapse with those inside?  Maybe I'm slow, but it took me a few minutes of staring at the test/reference renderings in current Firefox to figure that out, & realize why it's supposed to hold. :))

@@ +40,5 @@
> +  <span style="display:flow-root; border:1px solid">x</span>
> +</div>
> +
> +<span>
> +  <span style="display:flow-root"><div style="margin:20px">x</div></span>

(And it's not immediately clear what this chunk is testing.  Superficially, looking at it in isolation, it looks like it's testing an interaction between "flow-root" & "margin".  But in fact, I think it's *really* testing some after-effect from the floats above it.)

::: layout/reftests/css-display/display-flow-root-disabled-001-ref.html
@@ +38,5 @@
> +  <span style="border:1px solid">x</span>
> +</div>
> +
> +<span>
> +  <span style="display:inline-block; vertical-align:bottom"><div style="margin-top:20px">x</div></span>

In the interests of simplicity: do you really need this extra "display:inline-block;vertical-align:bottom" styling here? I'd think you could just use an unstyled <span>, since that's what the testcase's <span style="display:flow-root"> is really equivalent to, in a build without flow-root support.

(I tried removing these styles locally, too, it didn't seem to change the rendering.)
Attachment #8820790 - Flags: review?(dholbert) → review+
> Would it be possible/cleaner to just wire up "display:flow-root" to invoke NS_NewBlockFormattingContext directly?

Well, I started out doing that, but I thought it was too error prone to have to deal
with this in every place that calls NS_NewBlockFrame in nsCSSFrameConstructor.
This seems like a more robust solution.  I'm worried that people will forget to deal
with it when they add new NS_NewBlockFrame calls.
(In reply to Mats Palmgren (:mats) from comment #14)
> Well, I started out doing that, but I thought it was too error prone to have
> to deal
> with this in every place that calls NS_NewBlockFrame in
> nsCSSFrameConstructor.
> This seems like a more robust solution.  I'm worried that people will forget
> to deal
> with it when they add new NS_NewBlockFrame calls.

Fair enough. I guess what worries me about the current solution is that "NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT" is just the "magic word" for Block Formatting Context, without it being super-clear that that's what we're doing.  (This is already the case in a number of other places in the code, as I discovered when doing a DXR search.)

Perhaps as a helper-patch (either before or after this one), you could add a convenience #define called e.g. NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS in nsBlockFrame.h, and replace all current instances of this 2-bit bitmask with that clearer name?
We already have this sort of thing in nsBlockFrame::Init already,
so I decided to move it there instead to avoid having similar
code in two places.
Attachment #8820789 - Attachment is obsolete: true
Attachment #8820789 - Flags: review?(dholbert)
Attachment #8821331 - Flags: review?(dholbert)
(also removed an unnecessary null-check in nsLegendFrame.cpp because
allocating a frame is infallible these days)
Attachment #8821332 - Flags: review?(dholbert)
Comment on attachment 8821331 [details] [diff] [review]
part 2 - [css-display] Implement layout for display:flow-root.

Review of attachment 8821331 [details] [diff] [review]:
-----------------------------------------------------------------

r=me, with a few nits:

::: layout/base/nsCSSFrameConstructor.cpp
@@ +4835,5 @@
>      FCDATA_FOR_DISPLAY(StyleDisplay::WebkitBox,
>        FCDATA_DECL(FCDATA_MAY_NEED_SCROLLFRAME, NS_NewFlexContainerFrame)),
>      FCDATA_FOR_DISPLAY(StyleDisplay::WebkitInlineBox,
>        FCDATA_DECL(FCDATA_MAY_NEED_SCROLLFRAME, NS_NewFlexContainerFrame)),
> +    FCDATA_FOR_DISPLAY(StyleDisplay::FlowRoot, UNREACHABLE_FCDATA()),

Per end of comment 10, it seems more logical to group this FlowRoot line up higher with the Block line, here:
https://dxr.mozilla.org/mozilla-central/rev/f179934df0c1bab590c558485d419c7910e41325/layout/base/nsCSSFrameConstructor.cpp#4760-4761

...particularly since they both have the same UNREACHABLE_FCDATA behavior.

@@ +4840,2 @@
>    };
> +  static_assert(ArrayLength(sDisplayData) == size_t(StyleDisplay::FlowRoot) + 1,

And similarly: if you reorder nsStyleConsts in part 1 to insert FlowRoot alongside Block (instead of at the end), as I think you probably should, then you won't have to touch this line in this patch.
Attachment #8821331 - Flags: review?(dholbert) → review+
Comment on attachment 8821332 [details] [diff] [review]
part 2b - Replace "NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT" with NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS.

Review of attachment 8821332 [details] [diff] [review]:
-----------------------------------------------------------------

::: layout/generic/nsHTMLParts.h
@@ +29,5 @@
>  
>  // These are all the block specific frame bits, they are copied from
>  // the prev-in-flow to a newly created next-in-flow, except for the
>  // NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below.
> +#define NS_BLOCK_FLAGS_MASK (NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS | \

Perhaps better to leave this part unchanged (& leave it listing the specific individual bits), since this seems to be wanting* to exhaustively list a collection of all bits in this category?

(*I say it's "wanting" to exhaustively list the bits, per the code-comment "These are all the block specific frame bits".  But it doesn't actually succeed -- there are only 7 bits listed here, whereas there are 15 NS_BLOCK_* bits listed in nsFrameStateBits.h. I wonder if that's a problem? I guess it only gets used when copying bits from prev-in-flow, so the random other block bits don't get copied to NIFs I guess.)
Attachment #8821332 - Flags: review?(dholbert) → review+
> Perhaps better to leave this part unchanged...

I think it's better this way, because if we add a third BFC bit
at some point I think we definitely want to pick it up here.

> there are only 7 bits listed here, whereas there are 15 NS_BLOCK_* bits listed in nsFrameStateBits.h

Yeah, this setup is a bit odd.  Ideally, each bit in nsFrameStateBits.h should
declare if it should be copied to a next-in-flow or not.
(In reply to Daniel Holbert [:dholbert] from comment #18)
> And similarly: if you reorder nsStyleConsts in part 1 to insert FlowRoot
> alongside Block (instead of at the end), as I think you probably should,
> then you won't have to touch this line in this patch.

Fair enough.  I was a bit concerned that the list might be iterated
in order by some code, but it looks like the frame ctor code at least
use array access for sDisplayData.  I think I'll keep it at the end
of IsBlockInsideStyle/IsBlockOutsideStyle though, so that the values
are tested in most-commonly-used order for performance.
Pushed by mpalmgren@mozilla.com:
https://hg.mozilla.org/integration/mozilla-inbound/rev/0c9ef8b75095
part 1 - [css-display] Add style system support for display:flow-root.  Enable it by default.  r=dholbert
https://hg.mozilla.org/integration/mozilla-inbound/rev/bf6da68ef786
part 2 - [css-display] Implement layout for display:flow-root.  r=dholbert
https://hg.mozilla.org/integration/mozilla-inbound/rev/14b27f1d4157
part 2b - Replace "NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT" with NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS.  r=dholbert
https://hg.mozilla.org/integration/mozilla-inbound/rev/fae695cada69
part 3 - [css-display] Tests for display:flow-root.  r=dholbert
Mats, thanks for so fast implementation. Unfortunately, I've tested the feature in Firefox Nightly (20161225030206), and it behaves strangely:

while clearing floats really works (that's great), width of the element that has `display: flow-root` is somehow not 100% of its parent block, but depends on actual-content width instead (like a table).

If this is intended according to the spec (I'm not a guru of reading specs), then the width issue looks like an undesired and harmful side effect since _clearing floats_ is the _only_ thing web developers actually need from such a feature, so this should probably be solved on spec-level _before_ shipping the implemented feature, otherwise it's far less useful than it has been expected.
If I read the spec [1] correctly, the “short” value of `flow-root` corresponds to the “full” value of `block flow-root` (the latter itself does not currently work in Firefox, fwiw), so this is probably a Firefox-implementation bug.

[1] https://drafts.csswg.org/css-display-3/#display-value-summary
(In reply to Marat Tanalin | tanalin.com from comment #24)
> while clearing floats really works (that's great), width of the element that
> has `display: flow-root` is somehow not 100% of its parent block, but
> depends on actual-content width instead (like a table).

Can you file a new bug with a testcase please?
(In reply to Marat Tanalin | tanalin.com from comment #25)
> If I read the spec [1] correctly, the “short” value of `flow-root`
> corresponds to the “full” value of `block flow-root` (the latter itself does
> not currently work in Firefox, fwiw), so this is probably a
> Firefox-implementation bug.
> 
> [1] https://drafts.csswg.org/css-display-3/#display-value-summary

We're only implementing the single-keyword form of 'display' for now.
But yes, 'display:flow-root' is equivalent to 'display:block flow-root'
so that's the layout we want here.
Depends on: 1325970
Mats, please see the bug 1325970.
Documented the new value at https://developer.mozilla.org/en-US/docs/Web/CSS/display and mentioned it in the developer release notes at https://developer.mozilla.org/en-US/Firefox/Releases/53#CSS.

Mats, can you please review the documentation?

Sebastian
Flags: needinfo?(mats)
One nit on the documentation:  the 'flow-root' (and 'flow') values are missing
in the Syntax and Formal Syntax sections.  And the 'flow' value is missing altogether.
https://drafts.csswg.org/css-display-3/#inner-model
(we (and I suspect other UAs) don't implement 'flow' yet)

BTW, there is a word "display-value" in bold as the first word in the Values section -
I don't quite understand what purpose that serves.  Maybe just remove it?

I'm not sure what the relation is between Syntax and Formal Syntax, is the former
just an overview of the syntax we support (although some of the values are still
unsupported)? and the latter is the syntax from the spec?  If so, the latter
should probably be updated to what the spec says:
https://drafts.csswg.org/css-display/#the-display-properties

Other than it looks good to me.
Flags: needinfo?(mats)
... where "the syntax we support" means single-keyword display values only.
Are you planning to support `display: inline flow-root`?
I think that's the same as 'display:inline-block', per the table at:
https://drafts.csswg.org/css-display/#propdef-display

I guess you're asking: will we implement the general 'display' syntax?
I don't see any urgent need to do so, since most of the combinations that
authors want are already available as single keywords.  Also, the syntax
for 'display' has changed back and forth a few times so I don't know how
stable the current version is.
So there is really no need to implement the new display syntax, the specification is too unstable.

Thanks and happy new year!
(In reply to Mats Palmgren (:mats) from comment #30)
> One nit on the documentation:  the 'flow-root' (and 'flow') values are missing
> in the Syntax and Formal Syntax sections.  And the 'flow' value is missing altogether.
> https://drafts.csswg.org/css-display-3/#inner-model
> (we (and I suspect other UAs) don't implement 'flow' yet)

I've added it in https://github.com/mdn/data/pull/30.

> BTW, there is a word "display-value" in bold as the first word in the Values
> section -
> I don't quite understand what purpose that serves.  Maybe just remove it?

The "display-value" was obviously introduced as a variable for a syntax definition, which got replaced in the meantime.
I've reworked the "Values" section now and grouped the keywords by the value types defined in the spec.

> I'm not sure what the relation is between Syntax and Formal Syntax, is the former
> just an overview of the syntax we support (although some of the values are still
> unsupported)? and the latter is the syntax from the spec?  If so, the latter
> should probably be updated to what the spec says:
> https://drafts.csswg.org/css-display/#the-display-properties

The code block under "Syntax" are syntax examples. I've updated them according to the spec
The "Formal syntax", as you correctly assumed, reflects the definition in the specifications, which gets updated by the PR above.

> Other than it looks good to me.

Ok, thank you for the review! I assume the merge of the PR may still take a bit, because most people are in vacations. Though I already mark this as dev-doc-complete due to your feedback.

I wish you a Happy New Year!

Sebastian
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: