Commit 8ee75745 authored by Ian Kilpatrick's avatar Ian Kilpatrick Committed by Commit Bot

[layout] Track inflow-bounds within box fragment builder.

This patch tracks the bounds of any inflow children (no floats, or
out-of-flow positioned objects).

This is only done for scrollable containers.
This is used to correctly determine the layout-overflow of a fragment.

Bug: 1066616
Change-Id: I334176cdf61f6eda92573e169719dbf7a2f62995
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2432225Reviewed-by: default avatarMorten Stenshorne <mstensho@chromium.org>
Commit-Queue: Ian Kilpatrick <ikilpatrick@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812667}
parent f866eb74
......@@ -33,6 +33,10 @@ void LogicalRect::Unite(const LogicalRect& other) {
return;
}
UniteEvenIfEmpty(other);
}
void LogicalRect::UniteEvenIfEmpty(const LogicalRect& other) {
LogicalOffset new_end_offset(Max(EndOffset(), other.EndOffset()));
LogicalOffset new_start_offset(Min(offset, other.offset));
size = new_end_offset - new_start_offset;
......
......@@ -65,6 +65,7 @@ struct CORE_EXPORT LogicalRect {
}
void Unite(const LogicalRect&);
void UniteEvenIfEmpty(const LogicalRect&);
String ToString() const;
};
......
......@@ -47,6 +47,13 @@ struct CORE_EXPORT NGBoxStrut {
bool IsEmpty() const { return *this == NGBoxStrut(); }
void ClampNegativeToZero() {
inline_start = inline_start.ClampNegativeToZero();
inline_end = inline_end.ClampNegativeToZero();
block_start = block_start.ClampNegativeToZero();
block_end = block_end.ClampNegativeToZero();
}
inline NGPhysicalBoxStrut ConvertToPhysical(WritingMode, TextDirection) const;
// The following two operators exist primarily to have an easy way to access
......
......@@ -194,36 +194,87 @@ void NGBoxFragmentBuilder::AddResult(const NGLayoutResult& child_layout_result,
// maybe OOF objects. Investigate how to handle them.
}
}
AddChild(fragment, offset);
NGMarginStrut end_margin_strut = child_layout_result.EndMarginStrut();
// No margins should pierce outside formatting-context roots.
DCHECK(!fragment.IsFormattingContextRoot() || end_margin_strut.IsEmpty());
AddChild(fragment, offset, /* inline_container */ nullptr, &end_margin_strut);
if (fragment.IsBox())
PropagateBreak(child_layout_result);
}
void NGBoxFragmentBuilder::AddChild(const NGPhysicalContainerFragment& child,
const LogicalOffset& child_offset,
const LayoutInline* inline_container) {
const LayoutInline* inline_container,
const NGMarginStrut* margin_strut) {
LogicalOffset adjusted_offset = child_offset;
if (child.IsCSSBox() &&
box_type_ != NGPhysicalBoxFragment::NGBoxType::kInlineBox) {
// Apply the relative position offset.
const auto& box_child = To<NGPhysicalBoxFragment>(child);
if (box_child.Style().GetPosition() == EPosition::kRelative) {
adjusted_offset += ComputeRelativeOffsetForBoxFragment(
box_child, GetWritingDirection(), child_available_size_);
if (box_type_ != NGPhysicalBoxFragment::NGBoxType::kInlineBox) {
if (child.IsCSSBox()) {
// Apply the relative position offset.
const auto& box_child = To<NGPhysicalBoxFragment>(child);
if (box_child.Style().GetPosition() == EPosition::kRelative) {
adjusted_offset += ComputeRelativeOffsetForBoxFragment(
box_child, GetWritingDirection(), child_available_size_);
}
// The |may_have_descendant_above_block_start_| flag is used to determine
// if a fragment can be re-used when preceding floats are present. This
// is relatively rare, and is true if:
// - An inflow child is positioned above our block-start edge.
// - Any inflow descendants (within the same formatting-context) which
// *may* have a child positioned above our block-start edge.
if ((child_offset.block_offset < LayoutUnit() &&
!box_child.IsOutOfFlowPositioned()) ||
(!box_child.IsFormattingContextRoot() &&
box_child.MayHaveDescendantAboveBlockStart()))
may_have_descendant_above_block_start_ = true;
}
// The |may_have_descendant_above_block_start_| flag is used to determine
// if a fragment can be re-used when preceding floats are present. This is
// relatively rare, and is true if:
// - An inflow child is positioned above our block-start edge.
// - Any inflow descendants (within the same formatting-context) which
// *may* have a child positioned above our block-start edge.
if ((child_offset.block_offset < LayoutUnit() &&
!box_child.IsOutOfFlowPositioned()) ||
(!box_child.IsFormattingContextRoot() &&
box_child.MayHaveDescendantAboveBlockStart()))
may_have_descendant_above_block_start_ = true;
// If we are a scroll container, we need to track the maximum bounds of any
// inflow children (including line-boxes) to calculate the layout-overflow.
//
// This is used for determining the "padding-box" of the scroll container
// which is *sometimes* considered as part of the scrollable area. Inflow
// children contribute to this area, out-of-flow positioned children don't.
//
// Out-of-flow positioned children still contribute to the layout-overflow,
// but just don't influence where this padding is.
if (Node().IsScrollContainer() && !child.IsOutOfFlowPositioned()) {
NGBoxStrut margins;
if (child.IsCSSBox()) {
margins =
ComputeMarginsFor(child.Style(), child_available_size_.inline_size,
GetWritingMode(), Direction());
}
// If we are in block-flow layout we use the end *margin-strut* as the
// block-end "margin" (instead of just the block-end margin).
if (margin_strut) {
NGMarginStrut end_margin_strut = *margin_strut;
end_margin_strut.Append(margins.block_end, /* is_quirky */ false);
margins.block_end = end_margin_strut.Sum();
}
NGFragment fragment(GetWritingMode(), child);
// Use the original offset (*without* relative-positioning applied), and
// clamp any negative margins to zero.
margins.ClampNegativeToZero();
LogicalRect bounds = {
LogicalOffset(child_offset.inline_offset - margins.inline_start,
child_offset.block_offset - margins.block_start),
LogicalSize(
margins.inline_start + fragment.InlineSize() + margins.inline_end,
margins.block_start + fragment.BlockSize() + margins.block_end)};
// Even an empty (0x0) fragment contributes to the inflow-bounds.
if (!inflow_bounds_)
inflow_bounds_ = bounds;
else
inflow_bounds_->UniteEvenIfEmpty(bounds);
}
}
PropagateChildData(child, adjusted_offset, inline_container);
......
......@@ -207,7 +207,8 @@ class CORE_EXPORT NGBoxFragmentBuilder final
void AddChild(const NGPhysicalContainerFragment&,
const LogicalOffset&,
const LayoutInline* inline_container = nullptr);
const LayoutInline* inline_container = nullptr,
const NGMarginStrut* margin_strut = nullptr);
// Manually add a break token to the builder. Note that we're assuming that
// this break token is for content in the same flow as this parent.
......@@ -546,6 +547,7 @@ class CORE_EXPORT NGBoxFragmentBuilder final
LogicalSize child_available_size_;
LayoutUnit overflow_block_size_ = kIndefiniteSize;
LayoutUnit intrinsic_block_size_;
base::Optional<LogicalRect> inflow_bounds_;
NGFragmentItemsBuilder* items_builder_ = nullptr;
......
......@@ -181,6 +181,10 @@ class CORE_EXPORT NGLayoutInputNode {
return box_->GetNGPaginationBreakability() == LayoutBox::kForbidBreaks;
}
bool IsScrollContainer() const {
return IsBlock() && box_->IsScrollContainer();
}
bool CreatesNewFormattingContext() const {
return IsBlock() && box_->CreatesNewFormattingContext();
}
......
......@@ -50,7 +50,15 @@ scoped_refptr<const NGPhysicalBoxFragment> NGPhysicalBoxFragment::Create(
builder->initial_fragment_geometry_->padding.ConvertToPhysical(
builder->GetWritingMode(), builder->Direction());
bool has_padding = !padding.IsZero();
const PhysicalSize physical_size =
ToPhysicalSize(builder->Size(), builder->GetWritingMode());
WritingModeConverter converter(builder->GetWritingDirection(), physical_size);
base::Optional<PhysicalRect> inflow_bounds;
if (builder->inflow_bounds_)
inflow_bounds = converter.ToPhysical(*builder->inflow_bounds_);
const PhysicalRect layout_overflow;
bool has_layout_overflow = false;
bool has_rare_data =
......
<!DOCTYPE html>
<meta name="assert" content="This ensures that floats block-end margin contributes to the scrollable overflow.">
<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#scrollable" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/check-layout-th.js"></script>
<body onload="checkLayout('#target')">
<div id="target" style="width: 100px; height: 100px; overflow: scroll;" data-expected-scroll-height="220">
<div style="float: left; width: 50px; height: 200px; margin: 10px; background: lime;"></div>
</div>
<div id=log></div>
</body>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment