| // Copyright 2017 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/layout_ng_mixin.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "third_party/blink/renderer/core/editing/position_with_affinity.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_location.h" |
| #include "third_party/blink/renderer/core/layout/layout_block_flow.h" |
| #include "third_party/blink/renderer/core/layout/layout_table_caption.h" |
| #include "third_party/blink/renderer/core/layout/layout_table_cell.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_layout_utils.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_relative_utils.h" |
| #include "third_party/blink/renderer/core/paint/ng/ng_block_flow_painter.h" |
| #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" |
| |
| namespace blink { |
| |
| template <typename Base> |
| LayoutNGMixin<Base>::LayoutNGMixin(Element* element) : Base(element) { |
| static_assert( |
| std::is_base_of<LayoutBlockFlow, Base>::value, |
| "Base class of LayoutNGMixin must be LayoutBlockFlow or derived class."); |
| } |
| |
| template <typename Base> |
| LayoutNGMixin<Base>::~LayoutNGMixin() = default; |
| |
| template <typename Base> |
| bool LayoutNGMixin<Base>::IsOfType(LayoutObject::LayoutObjectType type) const { |
| return type == LayoutObject::kLayoutObjectNGMixin || Base::IsOfType(type); |
| } |
| |
| template <typename Base> |
| NGInlineNodeData* LayoutNGMixin<Base>::TakeNGInlineNodeData() { |
| return ng_inline_node_data_.release(); |
| } |
| |
| template <typename Base> |
| NGInlineNodeData* LayoutNGMixin<Base>::GetNGInlineNodeData() const { |
| DCHECK(ng_inline_node_data_); |
| return ng_inline_node_data_.get(); |
| } |
| |
| template <typename Base> |
| void LayoutNGMixin<Base>::ResetNGInlineNodeData() { |
| ng_inline_node_data_ = std::make_unique<NGInlineNodeData>(); |
| } |
| |
| // The current fragment from the last layout cycle for this box. |
| // When pre-NG layout calls functions of this block flow, fragment and/or |
| // LayoutResult are required to compute the result. |
| // TODO(kojii): Use the cached result for now, we may need to reconsider as the |
| // cache evolves. |
| template <typename Base> |
| const NGPhysicalBoxFragment* LayoutNGMixin<Base>::CurrentFragment() const { |
| if (cached_result_) |
| return ToNGPhysicalBoxFragment(cached_result_->PhysicalFragment().get()); |
| return nullptr; |
| } |
| |
| template <typename Base> |
| void LayoutNGMixin<Base>::AddOverflowFromChildren() { |
| // |ComputeOverflow()| calls this, which is called from |
| // |CopyFragmentDataToLayoutBox()| and |RecalcOverflowAfterStyleChange()|. |
| // Add overflow from the last layout cycle. |
| if (const NGPhysicalBoxFragment* physical_fragment = CurrentFragment()) { |
| AddScrollingOverflowFromChildren(); |
| if (Base::ChildrenInline()) { |
| Base::AddSelfVisualOverflow( |
| physical_fragment->SelfInkOverflow().ToLayoutFlippedRect( |
| physical_fragment->Style(), physical_fragment->Size())); |
| // TODO(kojii): If |RecalcOverflowAfterStyleChange()|, we need to |
| // re-compute glyph bounding box. How to detect it and how to re-compute |
| // is TBD. |
| Base::AddContentsVisualOverflow( |
| physical_fragment->ContentsInkOverflow().ToLayoutFlippedRect( |
| physical_fragment->Style(), physical_fragment->Size())); |
| // TODO(kojii): The above code computes visual overflow only, we fallback |
| // to LayoutBlock for AddLayoutOverflow() for now. It doesn't compute |
| // correctly without RootInlineBox though. |
| } |
| } |
| Base::AddOverflowFromChildren(); |
| } |
| |
| template <typename Base> |
| void LayoutNGMixin<Base>::AddScrollingOverflowFromChildren() { |
| bool children_inline = Base::ChildrenInline(); |
| |
| const NGPhysicalBoxFragment* physical_fragment = CurrentFragment(); |
| DCHECK(physical_fragment); |
| // inline-end LayoutOverflow padding spec is still undecided: |
| // https://github.com/w3c/csswg-drafts/issues/129 |
| // For backwards compatibility, if container clips overflow, |
| // padding is added to the inline-end for inline children. |
| base::Optional<NGPhysicalBoxStrut> padding_strut; |
| if (Base::HasOverflowClip()) { |
| padding_strut = |
| NGBoxStrut(LayoutUnit(), Base::PaddingEnd(), LayoutUnit(), LayoutUnit()) |
| .ConvertToPhysical(Base::StyleRef().GetWritingMode(), |
| Base::StyleRef().Direction()); |
| } |
| |
| NGPhysicalOffsetRect children_overflow; |
| |
| // Only add overflow for fragments NG has not reflected into Legacy. |
| // These fragments are: |
| // - inline fragments, |
| // - out of flow fragments whose css container is inline box. |
| // TODO(layout-dev) Transfroms also need to be applied to compute overflow |
| // correctly. NG is not yet transform-aware. crbug.com/855965 |
| if (!physical_fragment->Children().IsEmpty()) { |
| for (const auto& child : physical_fragment->Children()) { |
| NGPhysicalOffsetRect child_scrollable_overflow; |
| if (child->IsOutOfFlowPositioned()) { |
| child_scrollable_overflow = child->ScrollableOverflow(); |
| } else if (children_inline && child->IsLineBox()) { |
| DCHECK(child->IsLineBox()); |
| child_scrollable_overflow = |
| ToNGPhysicalLineBoxFragment(*child).ScrollableOverflow( |
| Base::Style(), physical_fragment->Size()); |
| if (padding_strut) |
| child_scrollable_overflow.Expand(*padding_strut); |
| } else { |
| continue; |
| } |
| child_scrollable_overflow.offset += child.Offset(); |
| children_overflow.Unite(child_scrollable_overflow); |
| } |
| } |
| |
| // If the scrollbar is on left, and the box is narrower than the scrollbar, |
| // the overflow is shifted to left by the amount the scrollbar overflows. |
| if (Base::ShouldPlaceVerticalScrollbarOnLeft()) { |
| LayoutUnit vertical_scrollbar_width = |
| LayoutUnit(Base::VerticalScrollbarWidth()); |
| if (physical_fragment->Size().width < vertical_scrollbar_width) { |
| children_overflow.offset.left += |
| physical_fragment->Size().width - vertical_scrollbar_width; |
| } |
| } |
| |
| // LayoutOverflow takes flipped blocks coordinates, adjust as needed. |
| LayoutRect children_flipped_overflow = children_overflow.ToLayoutFlippedRect( |
| physical_fragment->Style(), physical_fragment->Size()); |
| Base::AddLayoutOverflow(children_flipped_overflow); |
| } |
| |
| template <typename Base> |
| void LayoutNGMixin<Base>::AddOutlineRects( |
| Vector<LayoutRect>& rects, |
| const LayoutPoint& additional_offset, |
| LayoutObject::IncludeBlockVisualOverflowOrNot include_block_overflows) |
| const { |
| Base::AddOutlineRects(rects, additional_offset, include_block_overflows); |
| if (CurrentFragment()) { |
| CurrentFragment()->AddSelfOutlineRects(&rects, additional_offset); |
| } |
| } |
| |
| // Retrieve NGBaseline from the current fragment. |
| template <typename Base> |
| const NGBaseline* LayoutNGMixin<Base>::FragmentBaseline( |
| NGBaselineAlgorithmType type) const { |
| if (const NGPhysicalFragment* physical_fragment = CurrentFragment()) { |
| FontBaseline baseline_type = Base::StyleRef().GetFontBaseline(); |
| return ToNGPhysicalBoxFragment(physical_fragment) |
| ->Baseline({type, baseline_type}); |
| } |
| return nullptr; |
| } |
| |
| template <typename Base> |
| LayoutUnit LayoutNGMixin<Base>::FirstLineBoxBaseline() const { |
| if (Base::ChildrenInline()) { |
| if (const NGBaseline* baseline = |
| FragmentBaseline(NGBaselineAlgorithmType::kFirstLine)) { |
| return baseline->offset; |
| } |
| } |
| return Base::FirstLineBoxBaseline(); |
| } |
| |
| template <typename Base> |
| LayoutUnit LayoutNGMixin<Base>::InlineBlockBaseline( |
| LineDirectionMode line_direction) const { |
| if (Base::ChildrenInline()) { |
| if (const NGBaseline* baseline = |
| FragmentBaseline(NGBaselineAlgorithmType::kAtomicInline)) { |
| return baseline->offset; |
| } |
| } |
| return Base::InlineBlockBaseline(line_direction); |
| } |
| |
| template <typename Base> |
| scoped_refptr<NGLayoutResult> LayoutNGMixin<Base>::CachedLayoutResult( |
| const NGConstraintSpace& constraint_space, |
| NGBreakToken* break_token) const { |
| if (!RuntimeEnabledFeatures::LayoutNGFragmentCachingEnabled()) |
| return nullptr; |
| if (!cached_result_ || !Base::cached_constraint_space_ || break_token || |
| Base::NeedsLayout()) |
| return nullptr; |
| if (constraint_space != *Base::cached_constraint_space_) |
| return nullptr; |
| // The checks above should be enough to bail if layout is incomplete, but |
| // let's verify: |
| DCHECK( |
| IsBlockLayoutComplete(*Base::cached_constraint_space_, *cached_result_)); |
| // If we used to contain abspos items, we can't reuse the fragment, because |
| // we can't be sure that the list of items hasn't changed (as we bubble them |
| // up during layout). In the case of newly-added abspos items to this |
| // containing block, we will handle those by the NeedsLayout check above for |
| // now. |
| // TODO(layout-ng): Come up with a better solution for this |
| if (cached_result_->OutOfFlowPositionedDescendants().size()) |
| return nullptr; |
| return cached_result_->CloneWithoutOffset(); |
| } |
| |
| template <typename Base> |
| void LayoutNGMixin<Base>::SetCachedLayoutResult( |
| const NGConstraintSpace& constraint_space, |
| NGBreakToken* break_token, |
| scoped_refptr<NGLayoutResult> layout_result) { |
| if (break_token || layout_result->Status() != NGLayoutResult::kSuccess) { |
| // We can't cache these yet |
| return; |
| } |
| if (constraint_space.IsIntermediateLayout()) |
| return; |
| |
| Base::cached_constraint_space_ = &constraint_space; |
| cached_result_ = layout_result; |
| } |
| |
| template <typename Base> |
| scoped_refptr<NGLayoutResult> |
| LayoutNGMixin<Base>::CachedLayoutResultForTesting() { |
| return cached_result_; |
| } |
| |
| template <typename Base> |
| void LayoutNGMixin<Base>::SetPaintFragment( |
| NGPaintFragment* last_paint_fragment, |
| scoped_refptr<NGPaintFragment> paint_fragment) { |
| if (paint_fragment) { |
| // When paint fragment is replaced, the subtree needs paint invalidation to |
| // re-compute paint properties in NGPaintFragment. |
| Base::SetSubtreeShouldDoFullPaintInvalidation(); |
| } |
| |
| if (last_paint_fragment) { |
| last_paint_fragment->SetNext(std::move(paint_fragment)); |
| } else { |
| paint_fragment_ = std::move(paint_fragment); |
| } |
| } |
| |
| template <typename Base> |
| void LayoutNGMixin<Base>::SetPaintFragment( |
| const NGBreakToken* break_token, |
| scoped_refptr<const NGPhysicalFragment> fragment, |
| NGPhysicalOffset offset) { |
| // TODO(kojii): There are cases where the first call has break_token. |
| // Investigate why and handle appropriately. |
| // DCHECK(!break_token || paint_fragment_); |
| NGPaintFragment* last_paint_fragment = nullptr; |
| if (break_token && paint_fragment_) { |
| last_paint_fragment = paint_fragment_->Last(*break_token); |
| // TODO(kojii): Sometimes an unknown break_token is given. Need to |
| // investigate why, and handle appropriately. For now, just keep it to avoid |
| // crashes and use-after-free. |
| if (!last_paint_fragment) |
| last_paint_fragment = paint_fragment_->Last(); |
| DCHECK(last_paint_fragment); |
| } |
| SetPaintFragment( |
| last_paint_fragment, |
| fragment ? NGPaintFragment::Create(fragment, offset) : nullptr); |
| } |
| |
| template <typename Base> |
| void LayoutNGMixin<Base>::UpdatePaintFragmentFromCachedLayoutResult( |
| const NGBreakToken* break_token, |
| scoped_refptr<const NGPhysicalFragment> fragment, |
| NGPhysicalOffset fragment_offset) { |
| DCHECK(fragment); |
| // TODO(kojii): There are cases where the first call has break_token. |
| // Investigate why and handle appropriately. |
| // DCHECK(!break_token || paint_fragment_); |
| NGPaintFragment* paint_fragment = nullptr; |
| NGPaintFragment* last_paint_fragment = nullptr; |
| if (!break_token) { |
| paint_fragment = paint_fragment_.get(); |
| } else if (paint_fragment_) { |
| last_paint_fragment = paint_fragment_->Last(*break_token); |
| // TODO(kojii): Sometimes an unknown break_token is given. Need to |
| // investigate why, and handle appropriately. For now, just keep it to avoid |
| // crashes and use-after-free. |
| if (!last_paint_fragment) |
| last_paint_fragment = paint_fragment_->Last(); |
| DCHECK(last_paint_fragment); |
| paint_fragment = last_paint_fragment->Next(); |
| } |
| |
| if (!paint_fragment) { |
| SetPaintFragment( |
| last_paint_fragment, |
| NGPaintFragment::Create(std::move(fragment), fragment_offset)); |
| return; |
| } |
| |
| paint_fragment->UpdatePhysicalFragmentFromCachedLayoutResult(fragment); |
| } |
| |
| template <typename Base> |
| void LayoutNGMixin<Base>::InvalidateDisplayItemClients( |
| PaintInvalidationReason invalidation_reason) const { |
| if (NGPaintFragment* fragment = PaintFragment()) { |
| // TODO(koji): Should be in the PaintInvalidator, possibly with more logic |
| // ported from BlockFlowPaintInvalidator. |
| ObjectPaintInvalidator object_paint_invalidator(*this); |
| object_paint_invalidator.InvalidateDisplayItemClient(*fragment, |
| invalidation_reason); |
| return; |
| } |
| |
| LayoutBlockFlow::InvalidateDisplayItemClients(invalidation_reason); |
| } |
| |
| template <typename Base> |
| void LayoutNGMixin<Base>::Paint(const PaintInfo& paint_info) const { |
| if (PaintFragment()) |
| NGBlockFlowPainter(*this).Paint(paint_info); |
| else |
| LayoutBlockFlow::Paint(paint_info); |
| } |
| |
| template <typename Base> |
| bool LayoutNGMixin<Base>::NodeAtPoint( |
| HitTestResult& result, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| HitTestAction action) { |
| if (!PaintFragment()) { |
| return LayoutBlockFlow::NodeAtPoint(result, location_in_container, |
| accumulated_offset, action); |
| } |
| // In LayoutBox::NodeAtPoint() and subclass overrides, it is guaranteed that |
| // |accumulated_offset + Location()| equals the physical offset of the current |
| // LayoutBox in the paint layer, regardless of writing mode or whether the box |
| // was placed by NG or legacy. |
| const LayoutPoint physical_offset = accumulated_offset + Base::Location(); |
| if (!this->IsEffectiveRootScroller()) { |
| // Check if we need to do anything at all. |
| // If we have clipping, then we can't have any spillout. |
| LayoutRect overflow_box = Base::HasOverflowClip() |
| ? Base::BorderBoxRect() |
| : Base::VisualOverflowRect(); |
| overflow_box.MoveBy(physical_offset); |
| if (!location_in_container.Intersects(overflow_box)) |
| return false; |
| } |
| if (Base::IsInSelfHitTestingPhase(action) && Base::HasOverflowClip() && |
| Base::HitTestOverflowControl(result, location_in_container, |
| physical_offset)) |
| return true; |
| |
| return NGBlockFlowPainter(*this).NodeAtPoint(result, location_in_container, |
| physical_offset, action); |
| } |
| |
| template <typename Base> |
| PositionWithAffinity LayoutNGMixin<Base>::PositionForPoint( |
| const LayoutPoint& point) const { |
| if (Base::IsAtomicInlineLevel()) { |
| const PositionWithAffinity atomic_inline_position = |
| Base::PositionForPointIfOutsideAtomicInlineLevel(point); |
| if (atomic_inline_position.IsNotNull()) |
| return atomic_inline_position; |
| } |
| |
| if (!Base::ChildrenInline()) |
| return LayoutBlock::PositionForPoint(point); |
| |
| if (!PaintFragment()) |
| return Base::CreatePositionWithAffinity(0); |
| |
| const PositionWithAffinity ng_position = |
| PaintFragment()->PositionForPoint(NGPhysicalOffset(point)); |
| if (ng_position.IsNotNull()) |
| return ng_position; |
| return Base::CreatePositionWithAffinity(0); |
| } |
| |
| template class LayoutNGMixin<LayoutTableCaption>; |
| template class LayoutNGMixin<LayoutTableCell>; |
| template class LayoutNGMixin<LayoutBlockFlow>; |
| |
| } // namespace blink |