Skip to content

Commit b520bef

Browse files
mstenshoChromium LUCI CQ
authored andcommitted
Improve breaking before nested multicols and their rows.
1. Avoid breaking inside an inner multicol fragment if there were suboptimal breaks inside even if the inner multicol container didn't break in the outer fragmentation context. multicol-nested-018.html, multicol-nested-022.html and multicol-nested-023.html test this. 2. Properly constrain balanced column block-size to remaining space in the outer fragmentainer. We used to let unbreakable content override the remaining space, but this is wrong. This fixes forced-break-too-short-column.html and nested-short-first-row-extra-tall-line.html Store break appeal in NGLayoutResult, rather than in NGBreakToken. The reason is that we may have a violating break inside a fragment that happened inside a nested fragmentation context, even if the fragment doesn't necessarily break within the outer fragmentation context. Also create a proper break token inside a nested multicol container if there is none and we want to break before the first piece of column content. Calling SetDidBreakSelf() manually, like we used to, confused FinishFragmentation() into thinking that we were past the end of the block-end content box of the multicol container. This is tested by nested-with-padding.html Because of this change, the code in PreviousInnerFragmentainerIndex() needed an update, to ignore this break token, since break-before tokens don't have a sequence number. Remove the has_violating_descendant_break flag, and use the break appeal in the layout result instead. We had a column-balancing bug where where we were missing break-inside:avoid violations, because such violations don't affect the child's stored break appeal (the reason for this is explained in further detail inside CalculateBreakAppealInside()). Such violations were already propagated correctly to the builder's break appeal, so now it just works. This is tested by multicol-fill-balance-014.html . Also remove LayoutNGBlockFragmentation-specific baselines for moz-multicol3-column-balancing-break-inside-avoid-1.html because of this, as we now render identically to the legacy engine. This change fixes some existing tests. It also made outer-column-break-after-inner-spanner-2.html render correctly, but not according to the expectation, which was written for the legacy engine. The legacy engine fails to push the block with the BR inside entirely to the next outer fragmentainer. So replce it with a correct test: multicol-nested-017.html Bug: 829028 Change-Id: I13b2d95a0eb0407a82c8c24070a6dff4d2f620e1 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3150413 Commit-Queue: Morten Stenshorne <[email protected]> Reviewed-by: Ian Kilpatrick <[email protected]> Cr-Commit-Position: refs/heads/main@{#919825}
1 parent ed6cba7 commit b520bef

24 files changed

+282
-101
lines changed

third_party/blink/renderer/core/layout/ng/ng_block_break_token.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ NGBlockBreakToken::NGBlockBreakToken(PassKey key,
4242
builder.consumed_block_size_legacy_adjustment_),
4343
sequence_number_(builder.sequence_number_),
4444
const_num_children_(builder.child_break_tokens_.size()) {
45-
break_appeal_ = builder.break_appeal_;
4645
has_seen_all_children_ = builder.has_seen_all_children_;
4746
is_caused_by_column_spanner_ = builder.FoundColumnSpanner();
4847
is_at_block_end_ = builder.is_at_block_end_;

third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,6 @@ void NGBoxFragmentBuilder::AddBreakBeforeChild(
3535
DCHECK(!appeal || *appeal == kBreakAppealPerfect);
3636
} else if (appeal) {
3737
ClampBreakAppeal(*appeal);
38-
// If we're violating any orphans / widows or
39-
// break-{after,before,inside}:avoid requests, or if we're inserting a
40-
// last-resort break, remember this. If we're balancing columns, we may be
41-
// able to stretch the columns to resolve the situation.
42-
if (*appeal < kBreakAppealPerfect)
43-
has_violating_descendant_break_ = true;
4438
}
4539

4640
DCHECK(has_block_fragmentation_);
@@ -380,9 +374,6 @@ void NGBoxFragmentBuilder::PropagateBreak(
380374
PropagateSpaceShortage(child_layout_result.MinimalSpaceShortage());
381375
}
382376

383-
if (!is_fragmentation_context_root_)
384-
has_violating_descendant_break_ |= child_layout_result.HasViolatingBreak();
385-
386377
// If a spanner was found inside the child, we need to finish up and propagate
387378
// the spanner to the column layout algorithm, so that it can take care of it.
388379
if (UNLIKELY(ConstraintSpace()->IsInColumnBfc())) {

third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -368,12 +368,6 @@ class CORE_EXPORT NGBoxFragmentBuilder final
368368
void SetIsAtBlockEnd() { is_at_block_end_ = true; }
369369
bool IsAtBlockEnd() const { return is_at_block_end_; }
370370

371-
// True if we have inserted a break before or inside a descendant at a
372-
// less-than-ideal breakpoint, i.e. if the break appeal isn't perfect.
373-
bool HasViolatingDescendantBreak() const {
374-
return has_violating_descendant_break_;
375-
}
376-
377371
// See |NGPhysicalBoxFragment::InflowBounds|.
378372
void SetInflowBounds(const LogicalRect& inflow_bounds) {
379373
DCHECK_NE(box_type_, NGPhysicalBoxFragment::NGBoxType::kInlineBox);
@@ -619,7 +613,6 @@ class CORE_EXPORT NGBoxFragmentBuilder final
619613
bool is_math_fraction_ = false;
620614
bool is_math_operator_ = false;
621615
bool is_at_block_end_ = false;
622-
bool has_violating_descendant_break_ = false;
623616
LayoutUnit consumed_block_size_;
624617
LayoutUnit consumed_block_size_legacy_adjustment_;
625618
LayoutUnit block_offset_for_additional_columns_;

third_party/blink/renderer/core/layout/ng/ng_break_token.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
#include "base/dcheck_is_on.h"
99
#include "third_party/blink/renderer/core/core_export.h"
10-
#include "third_party/blink/renderer/core/layout/ng/ng_break_appeal.h"
1110
#include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h"
1211

1312
namespace blink {
@@ -47,10 +46,6 @@ class CORE_EXPORT NGBreakToken : public GarbageCollected<NGBreakToken> {
4746
box_, static_cast<NGLayoutInputNode::NGLayoutInputNodeType>(type_));
4847
}
4948

50-
NGBreakAppeal BreakAppeal() const {
51-
return static_cast<NGBreakAppeal>(break_appeal_);
52-
}
53-
5449
#if DCHECK_IS_ON()
5550
virtual String ToString() const;
5651
void ShowBreakTokenTree() const;
@@ -69,7 +64,6 @@ class CORE_EXPORT NGBreakToken : public GarbageCollected<NGBreakToken> {
6964
is_forced_break_(false),
7065
is_caused_by_column_spanner_(false),
7166
is_at_block_end_(false),
72-
break_appeal_(kBreakAppealPerfect),
7367
has_seen_all_children_(false) {
7468
DCHECK_EQ(type, static_cast<NGBreakTokenType>(node.Type()));
7569
}
@@ -102,11 +96,6 @@ class CORE_EXPORT NGBreakToken : public GarbageCollected<NGBreakToken> {
10296
// a parallel flow.
10397
unsigned is_at_block_end_ : 1;
10498

105-
// If the break is unforced, this is the appeal of the break. Higher is
106-
// better. Violating breaking rules decreases appeal. Forced breaks always
107-
// have perfect appeal.
108-
unsigned break_appeal_ : kNGBreakAppealBitsNeeded; // NGBreakAppeal
109-
11099
// All children of this container have been "seen" at this point. This means
111100
// that all children have been fully laid out, or have break tokens. No more
112101
// children left to discover.

third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -464,9 +464,14 @@ NGBreakStatus NGColumnLayoutAlgorithm::LayoutChildren() {
464464
// children, but "continue" layout, so that we produce a
465465
// fragment. Note that we normally don't want to break right after
466466
// initial border/padding, but will do so as a last resort. It's up to
467-
// our containing block to decide what's best. In case there is no
468-
// break token inside, we need to manually mark that we broke.
469-
container_builder_.SetDidBreakSelf();
467+
// our containing block to decide what's best. If there's no incoming
468+
// break token, it means that we're at the very start of column
469+
// layout, and we need to create a break token before the first
470+
// column.
471+
if (!child_break_token) {
472+
container_builder_.AddBreakBeforeChild(
473+
Node(), kBreakAppealLastResort, /* is_forced_break */ false);
474+
}
470475

471476
break;
472477
}
@@ -732,7 +737,7 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::LayoutRow(
732737
break;
733738
}
734739

735-
has_violating_break |= result->HasViolatingBreak();
740+
has_violating_break |= result->BreakAppeal() != kBreakAppealPerfect;
736741
column_inline_offset += column_inline_progression_;
737742

738743
if (result->HasForcedBreak())
@@ -762,11 +767,9 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::LayoutRow(
762767
// lower appeal than what we've seen so far. Anything that would cause
763768
// "too severe" breaking violations will be pushed to the next outer
764769
// fragmentainer.
765-
if (column_break_token) {
766-
min_break_appeal =
767-
std::min(min_break_appeal.value_or(kBreakAppealPerfect),
768-
column_break_token->BreakAppeal());
769-
}
770+
min_break_appeal =
771+
std::min(min_break_appeal.value_or(kBreakAppealPerfect),
772+
result->BreakAppeal());
770773

771774
// Avoid creating rows that are too short to hold monolithic content.
772775
// Bail, discarding all columns. Note that this is safe to do even if
@@ -1233,6 +1236,9 @@ LayoutUnit NGColumnLayoutAlgorithm::CalculateBalancedColumnBlockSize(
12331236
LayoutUnit NGColumnLayoutAlgorithm::ConstrainColumnBlockSize(
12341237
LayoutUnit size,
12351238
LayoutUnit row_offset) const {
1239+
// Avoid becoming shorter than the tallest piece of unbreakable content.
1240+
size = std::max(size, tallest_unbreakable_block_size_);
1241+
12361242
if (is_constrained_by_outer_fragmentation_context_) {
12371243
// Don't become too tall to fit in the outer fragmentation context.
12381244
LayoutUnit available_outer_space =
@@ -1241,9 +1247,6 @@ LayoutUnit NGColumnLayoutAlgorithm::ConstrainColumnBlockSize(
12411247
size = std::min(size, available_outer_space);
12421248
}
12431249

1244-
// But avoid becoming shorter than the tallest piece of unbreakable content.
1245-
size = std::max(size, tallest_unbreakable_block_size_);
1246-
12471250
// The {,min-,max-}block-size properties are specified on the multicol
12481251
// container, but here we're calculating the column block sizes inside the
12491252
// multicol container, which isn't exactly the same. We may shrink the column

third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -207,23 +207,26 @@ NGBreakAppeal CalculateBreakAppealBefore(const NGConstraintSpace& space,
207207
return break_appeal;
208208
}
209209

210-
NGBreakAppeal CalculateBreakAppealInside(const NGConstraintSpace& space,
211-
const NGLayoutResult& layout_result) {
210+
NGBreakAppeal CalculateBreakAppealInside(
211+
const NGConstraintSpace& space,
212+
const NGLayoutResult& layout_result,
213+
absl::optional<NGBreakAppeal> hypothetical_appeal) {
212214
if (layout_result.HasForcedBreak())
213215
return kBreakAppealPerfect;
214216
const auto& physical_fragment = layout_result.PhysicalFragment();
215-
NGBreakAppeal appeal = kBreakAppealLastResort;
216-
// If we actually broke, get the appeal from the break token. Otherwise, get
217-
// the early break appeal.
218-
if (const auto* block_break_token =
219-
DynamicTo<NGBlockBreakToken>(physical_fragment.BreakToken())) {
220-
appeal = block_break_token->BreakAppeal();
221-
} else if (const NGEarlyBreak* early_break = layout_result.GetEarlyBreak()) {
222-
appeal = early_break->BreakAppeal();
217+
const auto* break_token =
218+
DynamicTo<NGBlockBreakToken>(physical_fragment.BreakToken());
219+
NGBreakAppeal appeal;
220+
bool consider_break_inside_avoidance;
221+
if (hypothetical_appeal) {
222+
// The hypothetical appeal of breaking inside should only be considered if
223+
// we haven't actually broken.
224+
DCHECK(!break_token);
225+
appeal = *hypothetical_appeal;
226+
consider_break_inside_avoidance = true;
223227
} else {
224-
// If we have neither a break token nor an early-break object, we shouldn't
225-
// really be here.
226-
NOTREACHED();
228+
appeal = layout_result.BreakAppeal();
229+
consider_break_inside_avoidance = break_token;
227230
}
228231

229232
// We don't let break-inside:avoid affect the child's stored break appeal, but
@@ -233,7 +236,8 @@ NGBreakAppeal CalculateBreakAppealInside(const NGConstraintSpace& space,
233236
// rule on the child itself. This prevents us from violating more rules than
234237
// necessary: if we need to break inside the child (even if it should be
235238
// avoided), we'll at least break at the most appealing location inside.
236-
if (appeal > kBreakAppealViolatingBreakAvoid &&
239+
if (consider_break_inside_avoidance &&
240+
appeal > kBreakAppealViolatingBreakAvoid &&
237241
IsAvoidBreakValue(space, physical_fragment.Style().BreakInside()))
238242
appeal = kBreakAppealViolatingBreakAvoid;
239243
return appeal;
@@ -781,14 +785,14 @@ bool MovePastBreakpoint(const NGConstraintSpace& space,
781785
return false;
782786
}
783787

784-
if (break_token) {
785-
// The block child broke inside. We now need to decide whether to keep that
786-
// break, or if it would be better to break before it.
787-
NGBreakAppeal appeal_inside =
788-
CalculateBreakAppealInside(space, layout_result);
789-
// Allow breaking inside if it has the same appeal or higher than breaking
790-
// before or breaking earlier. Also, if breaking before is impossible, break
791-
// inside regardless of appeal.
788+
NGBreakAppeal appeal_inside =
789+
CalculateBreakAppealInside(space, layout_result);
790+
if (break_token || appeal_inside < kBreakAppealPerfect) {
791+
// The block child broke inside, either in this fragmentation context, or in
792+
// an inner one. We now need to decide whether to keep that break, or if it
793+
// would be better to break before it. Allow breaking inside if it has the
794+
// same appeal or higher than breaking before or breaking earlier. Also, if
795+
// breaking before is impossible, break inside regardless of appeal.
792796
if (refuse_break_before)
793797
return true;
794798
if (appeal_inside >= appeal_before &&
@@ -828,7 +832,8 @@ void UpdateEarlyBreakAtBlockChild(const NGConstraintSpace& space,
828832
// See if there's a good breakpoint inside the child.
829833
NGBreakAppeal appeal_inside = kBreakAppealLastResort;
830834
if (const NGEarlyBreak* breakpoint = layout_result.GetEarlyBreak()) {
831-
appeal_inside = CalculateBreakAppealInside(space, layout_result);
835+
appeal_inside = CalculateBreakAppealInside(space, layout_result,
836+
breakpoint->BreakAppeal());
832837
if (!builder->HasEarlyBreak() ||
833838
builder->EarlyBreak().BreakAppeal() <= breakpoint->BreakAppeal()) {
834839
// Found a good breakpoint inside the child. Add the child to the early
@@ -1030,7 +1035,13 @@ wtf_size_t PreviousInnerFragmentainerIndex(
10301035
// Not a fragmentainer (probably a spanner)
10311036
continue;
10321037
}
1033-
idx = To<NGBlockBreakToken>(child_token.Get())->SequenceNumber() + 1;
1038+
const auto& block_child_token = To<NGBlockBreakToken>(*child_token);
1039+
// There may be a break before the first column, if we had to break
1040+
// between the block-start border/padding of the multicol container and
1041+
// its contents due to space shortage.
1042+
if (block_child_token.IsBreakBefore())
1043+
continue;
1044+
idx = block_child_token.SequenceNumber() + 1;
10341045
break;
10351046
}
10361047
}

third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,14 @@ NGBreakAppeal CalculateBreakAppealBefore(const NGConstraintSpace&,
7474
const NGBoxFragmentBuilder&,
7575
bool has_container_separation);
7676

77-
// Calculate the appeal of breaking inside this child.
78-
NGBreakAppeal CalculateBreakAppealInside(const NGConstraintSpace& space,
79-
const NGLayoutResult&);
77+
// Calculate the appeal of breaking inside this child. The appeal is based on
78+
// the one stored in the layout result, unless hypothetical_appeal is specified.
79+
// hypothetical_appeal is used to assess the appeal at breakpoints where we
80+
// didn't break, but still need to consider (see NGEarlyBreak).
81+
NGBreakAppeal CalculateBreakAppealInside(
82+
const NGConstraintSpace& space,
83+
const NGLayoutResult&,
84+
absl::optional<NGBreakAppeal> hypothetical_appeal = absl::nullopt);
8085

8186
// To ensure content progression, we need fragmentainers to hold something
8287
// larger than 0. The spec says that fragmentainers have to accept at least 1px

third_party/blink/renderer/core/layout/ng/ng_layout_result.cc

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,7 @@ NGLayoutResult::NGLayoutResult(
8484
rare_data->minimal_space_shortage = builder->minimal_space_shortage_;
8585
}
8686

87-
bitfields_.has_violating_break =
88-
builder->HasViolatingDescendantBreak() ||
89-
(builder->DidBreakSelf() &&
90-
builder->break_appeal_ < kBreakAppealPerfect);
91-
87+
bitfields_.break_appeal = builder->break_appeal_;
9288
bitfields_.initial_break_before = static_cast<unsigned>(
9389
builder->initial_break_before_.value_or(EBreakBetween::kAuto));
9490
bitfields_.final_break_after =

third_party/blink/renderer/core/layout/ng/ng_layout_result.h

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h"
1616
#include "third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.h"
1717
#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
18+
#include "third_party/blink/renderer/core/layout/ng/ng_break_appeal.h"
1819
#include "third_party/blink/renderer/core/layout/ng/ng_early_break.h"
1920
#include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h"
2021
#include "third_party/blink/renderer/core/layout/ng/ng_fragment.h"
@@ -223,11 +224,25 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> {
223224
return rare_data_->tallest_unbreakable_block_size;
224225
}
225226

226-
// Return true if we weren't able to honor all break avoidance hints requested
227-
// by break-{after,before,inside}:avoid or orphans / widows, or if we had to
228-
// break at an invalid breakpoint (e.g. before/after the first/last
229-
// child). This is used for column balancing.
230-
bool HasViolatingBreak() const { return bitfields_.has_violating_break; }
227+
// Return the (lowest) appeal among any unforced breaks inside the resulting
228+
// fragment (or kBreakAppealPerfect if there are no such breaks).
229+
//
230+
// A higher value is better. Violating breaking rules decreases appeal. Forced
231+
// breaks always have perfect appeal.
232+
//
233+
// If a node breaks, the resulting fragment usually carries an outgoing break
234+
// token, but this isn't necessarily the case if the break happened inside an
235+
// inner fragmentation context. The block-size of an inner multicol is
236+
// constrained by the available block-size in the outer fragmentation
237+
// context. This may cause suboptimal column breaks inside. The entire inner
238+
// multicol container may fit in the outer fragmentation context, but we may
239+
// also need to consider the inner column breaks (in an inner fragmentation
240+
// context). If there are any suboptimal breaks, we may want to push the
241+
// entire multicol container to the next outer fragmentainer, if it's likely
242+
// that we'll avoid suboptimal column breaks inside that way.
243+
NGBreakAppeal BreakAppeal() const {
244+
return static_cast<NGBreakAppeal>(bitfields_.break_appeal);
245+
}
231246

232247
SerializedScriptValue* CustomLayoutData() const {
233248
return HasRareData() ? rare_data_->custom_layout_data.get() : nullptr;
@@ -528,7 +543,7 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> {
528543
can_use_out_of_flow_positioned_first_tier_cache(false),
529544
is_bfc_block_offset_nullopt(false),
530545
has_forced_break(false),
531-
has_violating_break(false),
546+
break_appeal(kBreakAppealPerfect),
532547
is_empty_spanner_parent(false),
533548
is_self_collapsing(is_self_collapsing),
534549
is_pushed_by_floats(is_pushed_by_floats),
@@ -548,7 +563,7 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> {
548563
unsigned is_bfc_block_offset_nullopt : 1;
549564

550565
unsigned has_forced_break : 1;
551-
unsigned has_violating_break : 1;
566+
unsigned break_appeal : kNGBreakAppealBitsNeeded;
552567
unsigned is_empty_spanner_parent : 1;
553568

554569
unsigned is_self_collapsing : 1;

0 commit comments

Comments
 (0)