| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/layout/ng/ng_layout_overflow_calculator.h" |
| |
| #include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" |
| #include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_fragment.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" |
| |
| namespace blink { |
| |
| // static |
| PhysicalRect NGLayoutOverflowCalculator::RecalculateLayoutOverflowForFragment( |
| const NGPhysicalBoxFragment& fragment) { |
| DCHECK(!fragment.IsLegacyLayoutRoot()); |
| const NGBlockNode node(const_cast<LayoutBox*>( |
| To<LayoutBox>(fragment.GetSelfOrContainerLayoutObject()))); |
| const WritingDirectionMode writing_direction = |
| node.Style().GetWritingDirection(); |
| |
| // TODO(ikilpatrick): The final computed scrollbars for a fragment should |
| // likely live on the NGPhysicalBoxFragment. |
| NGPhysicalBoxStrut scrollbar; |
| if (fragment.IsCSSBox()) { |
| scrollbar = ComputeScrollbarsForNonAnonymous(node).ConvertToPhysical( |
| writing_direction); |
| } |
| |
| NGLayoutOverflowCalculator calculator( |
| node, fragment.IsCSSBox(), fragment.Borders(), scrollbar, |
| fragment.Padding(), fragment.Size(), writing_direction); |
| |
| if (const NGFragmentItems* items = fragment.Items()) |
| calculator.AddItems(*items); |
| |
| for (const auto& child : fragment.PostLayoutChildren()) { |
| const auto* box_fragment = |
| DynamicTo<NGPhysicalBoxFragment>(*child.fragment); |
| if (!box_fragment) |
| continue; |
| |
| if (box_fragment->IsFragmentainerBox()) { |
| // When this function is called nothing has updated the layout-overflow |
| // of any fragmentainers (as they are not directly associated with a |
| // layout-object). Recalculate their layout-overflow directly. |
| PhysicalRect child_overflow = |
| RecalculateLayoutOverflowForFragment(*box_fragment); |
| child_overflow.offset += child.offset; |
| calculator.AddOverflow(child_overflow); |
| } else { |
| calculator.AddChild(*box_fragment, child.offset); |
| } |
| } |
| |
| return calculator.Result(fragment.InflowBounds()); |
| } |
| |
| NGLayoutOverflowCalculator::NGLayoutOverflowCalculator( |
| const NGBlockNode& node, |
| bool is_css_box, |
| const NGPhysicalBoxStrut& borders, |
| const NGPhysicalBoxStrut& scrollbar, |
| const NGPhysicalBoxStrut& padding, |
| PhysicalSize size, |
| WritingDirectionMode writing_direction) |
| : node_(node), |
| writing_direction_(writing_direction), |
| is_scroll_container_(is_css_box && node_.IsScrollContainer()), |
| has_left_overflow_(is_css_box && node_.HasLeftOverflow()), |
| has_top_overflow_(is_css_box && node_.HasTopOverflow()), |
| has_non_visible_overflow_(is_css_box && node_.HasNonVisibleOverflow()), |
| padding_(padding), |
| size_(size) { |
| const auto border_scrollbar = borders + scrollbar; |
| |
| // TODO(layout-dev): This isn't correct for <fieldset> elements as we may |
| // have a legend which is taller than the block-start border. |
| padding_rect_ = {PhysicalOffset(border_scrollbar.left, border_scrollbar.top), |
| PhysicalSize((size_.width - border_scrollbar.HorizontalSum()) |
| .ClampNegativeToZero(), |
| (size_.height - border_scrollbar.VerticalSum()) |
| .ClampNegativeToZero())}; |
| layout_overflow_ = padding_rect_; |
| } |
| |
| const PhysicalRect NGLayoutOverflowCalculator::Result( |
| const base::Optional<PhysicalRect> inflow_bounds) { |
| if (!inflow_bounds || !is_scroll_container_) |
| return layout_overflow_; |
| |
| PhysicalOffset start_offset = inflow_bounds->MinXMinYCorner() - |
| PhysicalOffset(padding_.left, padding_.top); |
| PhysicalOffset end_offset = inflow_bounds->MaxXMaxYCorner() + |
| PhysicalOffset(padding_.right, padding_.bottom); |
| |
| PhysicalRect inflow_overflow = { |
| start_offset, PhysicalSize(end_offset.left - start_offset.left, |
| end_offset.top - start_offset.top)}; |
| inflow_overflow = AdjustOverflowForScrollOrigin(inflow_overflow); |
| |
| PhysicalRect normal_overflow = layout_overflow_; |
| normal_overflow.UniteEvenIfEmpty(inflow_overflow); |
| |
| if (node_.IsInlineFormattingContextRoot()) |
| return normal_overflow; |
| |
| WritingModeConverter converter(writing_direction_, size_); |
| |
| LogicalRect block_end_padding_rect = { |
| LogicalOffset(converter.ToLogical(padding_rect_).offset.inline_offset, |
| converter.ToLogical(*inflow_bounds).BlockEndOffset()), |
| LogicalSize(LayoutUnit(), |
| padding_.ConvertToLogical(writing_direction_).block_end)}; |
| |
| PhysicalRect alternate_overflow = layout_overflow_; |
| alternate_overflow.UniteEvenIfEmpty(AdjustOverflowForScrollOrigin( |
| converter.ToPhysical(block_end_padding_rect))); |
| |
| // We'd like everything to be |normal_overflow|, lets see what the impact |
| // would be. |
| if (node_.Style().OverflowInlineDirection() == EOverflow::kAuto || |
| node_.Style().OverflowInlineDirection() == EOverflow::kScroll) { |
| if (alternate_overflow.size.width != normal_overflow.size.width) { |
| if (alternate_overflow.size.width != padding_rect_.size.width) { |
| UseCounter::Count( |
| node_.GetDocument(), |
| node_.IsFlexibleBox() |
| ? WebFeature::kNewLayoutOverflowDifferentAndAlreadyScrollsFlex |
| : WebFeature:: |
| kNewLayoutOverflowDifferentAndAlreadyScrollsBlock); |
| } else { |
| UseCounter::Count(node_.GetDocument(), |
| node_.IsFlexibleBox() |
| ? WebFeature::kNewLayoutOverflowDifferentFlex |
| : WebFeature::kNewLayoutOverflowDifferentBlock); |
| } |
| } |
| } |
| |
| return alternate_overflow; |
| } |
| |
| template <typename Items> |
| void NGLayoutOverflowCalculator::AddItemsInternal(const Items& items) { |
| bool has_hanging = false; |
| PhysicalRect line_rect; |
| |
| for (const auto& item : items) { |
| if (const auto* line_box = item->LineBoxFragment()) { |
| has_hanging = line_box->HasHanging(); |
| line_rect = item->RectInContainerFragment(); |
| |
| if (line_rect.IsEmpty()) |
| continue; |
| |
| // Currently line-boxes don't contribute overflow in the block-axis. This |
| // was added for web-compat reasons. |
| PhysicalRect child_overflow = line_rect; |
| if (writing_direction_.IsHorizontal()) |
| child_overflow.size.height = LayoutUnit(); |
| else |
| child_overflow.size.width = LayoutUnit(); |
| |
| layout_overflow_.UniteEvenIfEmpty(child_overflow); |
| continue; |
| } |
| |
| if (item->IsText()) { |
| PhysicalRect child_overflow = item->RectInContainerFragment(); |
| |
| // Adjust the text's overflow if the line-box has hanging. |
| if (UNLIKELY(has_hanging)) |
| child_overflow = AdjustOverflowForHanging(line_rect, child_overflow); |
| |
| AddOverflow(child_overflow); |
| continue; |
| } |
| |
| if (const auto* child_box_fragment = item->BoxFragment()) { |
| // Use the default box-fragment overflow logic. |
| PhysicalRect child_overflow = |
| LayoutOverflowForPropagation(*child_box_fragment); |
| child_overflow.offset += item->OffsetInContainerFragment(); |
| |
| // Only inline-boxes (not atomic-inlines) should be adjusted if the |
| // line-box has hanging. |
| if (child_box_fragment->IsInlineBox() && has_hanging) |
| child_overflow = AdjustOverflowForHanging(line_rect, child_overflow); |
| |
| AddOverflow(child_overflow); |
| continue; |
| } |
| } |
| } |
| |
| void NGLayoutOverflowCalculator::AddItems( |
| const NGFragmentItemsBuilder::ItemWithOffsetList& items) { |
| AddItemsInternal(items); |
| } |
| |
| void NGLayoutOverflowCalculator::AddItems(const NGFragmentItems& items) { |
| AddItemsInternal(items.Items()); |
| } |
| |
| PhysicalRect NGLayoutOverflowCalculator::AdjustOverflowForHanging( |
| const PhysicalRect& line_rect, |
| PhysicalRect overflow) { |
| if (writing_direction_.IsHorizontal()) { |
| if (overflow.offset.left < line_rect.offset.left) |
| overflow.offset.left = line_rect.offset.left; |
| if (overflow.Right() > line_rect.Right()) |
| overflow.ShiftRightEdgeTo(line_rect.Right()); |
| } else { |
| if (overflow.offset.top < line_rect.offset.top) |
| overflow.offset.top = line_rect.offset.top; |
| if (overflow.Bottom() > line_rect.Bottom()) |
| overflow.ShiftBottomEdgeTo(line_rect.Bottom()); |
| } |
| |
| return overflow; |
| } |
| |
| PhysicalRect NGLayoutOverflowCalculator::AdjustOverflowForScrollOrigin( |
| const PhysicalRect& overflow) { |
| LayoutUnit left_offset = |
| has_left_overflow_ |
| ? std::min(padding_rect_.Right(), overflow.offset.left) |
| : std::max(padding_rect_.offset.left, overflow.offset.left); |
| |
| LayoutUnit right_offset = |
| has_left_overflow_ |
| ? std::min(padding_rect_.Right(), overflow.Right()) |
| : std::max(padding_rect_.offset.left, overflow.Right()); |
| |
| LayoutUnit top_offset = |
| has_top_overflow_ |
| ? std::min(padding_rect_.Bottom(), overflow.offset.top) |
| : std::max(padding_rect_.offset.top, overflow.offset.top); |
| |
| LayoutUnit bottom_offset = |
| has_top_overflow_ ? std::min(padding_rect_.Bottom(), overflow.Bottom()) |
| : std::max(padding_rect_.offset.top, overflow.Bottom()); |
| |
| return {PhysicalOffset(left_offset, top_offset), |
| PhysicalSize(right_offset - left_offset, bottom_offset - top_offset)}; |
| } |
| |
| PhysicalRect NGLayoutOverflowCalculator::LayoutOverflowForPropagation( |
| const NGPhysicalBoxFragment& child_fragment) { |
| // If the fragment is anonymous, just return its layout-overflow (don't apply |
| // any incorrect transforms, etc). |
| if (!child_fragment.IsCSSBox()) |
| return child_fragment.LayoutOverflow(); |
| |
| PhysicalRect overflow = {{}, child_fragment.Size()}; |
| const auto& child_style = child_fragment.Style(); |
| if (!child_fragment.ShouldApplyLayoutContainment() && |
| (!child_fragment.ShouldClipOverflowAlongBothAxis() || |
| child_style.OverflowClipMargin() != LayoutUnit()) && |
| !child_fragment.IsInlineBox()) { |
| PhysicalRect child_overflow = child_fragment.LayoutOverflow(); |
| if (child_fragment.HasNonVisibleOverflow()) { |
| const OverflowClipAxes overflow_clip_axes = |
| child_fragment.GetOverflowClipAxes(); |
| const LayoutUnit overflow_clip_margin = child_style.OverflowClipMargin(); |
| if (overflow_clip_margin != LayoutUnit()) { |
| // overflow_clip_margin should only be set if 'overflow' is 'clip' along |
| // both axis. |
| DCHECK_EQ(overflow_clip_axes, kOverflowClipBothAxis); |
| PhysicalRect child_padding_rect({}, child_fragment.Size()); |
| child_padding_rect.Contract(child_fragment.Borders()); |
| child_padding_rect.Inflate(overflow_clip_margin); |
| child_overflow.Intersect(child_padding_rect); |
| } else { |
| if (overflow_clip_axes & kOverflowClipX) { |
| child_overflow.offset.left = LayoutUnit(); |
| child_overflow.size.width = child_fragment.Size().width; |
| } |
| if (overflow_clip_axes & kOverflowClipY) { |
| child_overflow.offset.top = LayoutUnit(); |
| child_overflow.size.height = child_fragment.Size().height; |
| } |
| } |
| } |
| overflow.UniteEvenIfEmpty(child_overflow); |
| } |
| |
| // Apply any transforms to the overflow. |
| if (base::Optional<TransformationMatrix> transform = |
| node_.GetTransformForChildFragment(child_fragment, size_)) { |
| overflow = |
| PhysicalRect::EnclosingRect(transform->MapRect(FloatRect(overflow))); |
| } |
| |
| return overflow; |
| } |
| |
| } // namespace blink |