blob: 7c942eb88aefa712e3d0b329497345f46594234e [file] [log] [blame]
// Copyright 2019 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_block_flow_mixin.h"
#include <memory>
#include <utility>
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/layout/hit_test_location.h"
#include "third_party/blink/renderer/core/layout/layout_analyzer.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.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/layout_box_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.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_box_fragment_painter.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
namespace blink {
template <typename Base>
LayoutNGBlockFlowMixin<Base>::LayoutNGBlockFlowMixin(Element* element)
: LayoutNGMixin<Base>(element) {
static_assert(std::is_base_of<LayoutBlockFlow, Base>::value,
"Base class of LayoutNGBlockFlowMixin must be LayoutBlockFlow "
"or derived class.");
}
template <typename Base>
LayoutNGBlockFlowMixin<Base>::~LayoutNGBlockFlowMixin() = default;
template <typename Base>
void LayoutNGBlockFlowMixin<Base>::StyleDidChange(
StyleDifference diff,
const ComputedStyle* old_style) {
Base::StyleDidChange(diff, old_style);
if (diff.NeedsReshape()) {
Base::SetNeedsCollectInlines();
}
}
template <typename Base>
NGInlineNodeData* LayoutNGBlockFlowMixin<Base>::TakeNGInlineNodeData() {
return ng_inline_node_data_.release();
}
template <typename Base>
NGInlineNodeData* LayoutNGBlockFlowMixin<Base>::GetNGInlineNodeData() const {
DCHECK(ng_inline_node_data_);
return ng_inline_node_data_.get();
}
template <typename Base>
void LayoutNGBlockFlowMixin<Base>::ResetNGInlineNodeData() {
ng_inline_node_data_ = std::make_unique<NGInlineNodeData>();
}
template <typename Base>
void LayoutNGBlockFlowMixin<Base>::ClearNGInlineNodeData() {
ng_inline_node_data_.reset();
}
template <typename Base>
void LayoutNGBlockFlowMixin<Base>::AddLayoutOverflowFromChildren() {
if (Base::ChildLayoutBlockedByDisplayLock())
return;
// |ComputeOverflow()| calls this, which is called from
// |CopyFragmentDataToLayoutBox()| and |RecalcOverflow()|.
// Add overflow from the last layout cycle.
// TODO(chrishtr): do we need to condition on CurrentFragment()? Why?
if (CurrentFragment()) {
AddScrollingOverflowFromChildren();
}
Base::AddLayoutOverflowFromChildren();
}
template <typename Base>
void LayoutNGBlockFlowMixin<Base>::AddScrollingOverflowFromChildren() {
const NGPhysicalBoxFragment* physical_fragment = CurrentFragment();
DCHECK(physical_fragment);
PhysicalRect children_overflow =
physical_fragment->ScrollableOverflowFromChildren(
NGPhysicalFragment::kNormalHeight);
// LayoutOverflow takes flipped blocks coordinates, adjust as needed.
const ComputedStyle& style = physical_fragment->Style();
LayoutRect children_flipped_overflow =
children_overflow.ToLayoutFlippedRect(style, physical_fragment->Size());
Base::AddLayoutOverflow(children_flipped_overflow);
}
template <typename Base>
void LayoutNGBlockFlowMixin<Base>::AddOutlineRects(
Vector<PhysicalRect>& rects,
const PhysicalOffset& additional_offset,
NGOutlineType include_block_overflows) const {
// TODO(crbug.com/1145048): Currently |NGBoxPhysicalFragment| does not support
// NG block fragmentation. Fallback to the legacy code path.
if (Base::PhysicalFragmentCount() == 1) {
const NGPhysicalBoxFragment* fragment = Base::GetPhysicalFragment(0);
if (fragment->HasItems()) {
fragment->AddSelfOutlineRects(additional_offset, include_block_overflows,
&rects);
return;
}
}
Base::AddOutlineRects(rects, additional_offset, include_block_overflows);
}
template <typename Base>
LayoutUnit LayoutNGBlockFlowMixin<Base>::FirstLineBoxBaseline() const {
// Please see |Paint()| for these DCHECKs.
DCHECK(!Base::CanTraversePhysicalFragments() ||
!Base::Parent()->CanTraversePhysicalFragments());
DCHECK_LE(Base::PhysicalFragmentCount(), 1u);
if (const base::Optional<LayoutUnit> baseline =
Base::FirstLineBoxBaselineOverride())
return *baseline;
if (Base::PhysicalFragmentCount()) {
const NGPhysicalBoxFragment* fragment = Base::GetPhysicalFragment(0);
DCHECK(fragment);
if (base::Optional<LayoutUnit> offset = fragment->Baseline())
return *offset;
}
// This logic is in |LayoutBlock|, but we cannot call |Base| because doing so
// may traverse |LayoutObject| tree, which may call this function for a child,
// but the child may be block fragmented.
if (Base::ChildrenInline()) {
return Base::EmptyLineBaseline(
Base::IsHorizontalWritingMode() ? kHorizontalLine : kVerticalLine);
}
return LayoutUnit(-1);
}
template <typename Base>
LayoutUnit LayoutNGBlockFlowMixin<Base>::InlineBlockBaseline(
LineDirectionMode line_direction) const {
// Please see |Paint()| for these DCHECKs.
DCHECK(!Base::CanTraversePhysicalFragments() ||
!Base::Parent()->CanTraversePhysicalFragments());
DCHECK_LE(Base::PhysicalFragmentCount(), 1u);
if (const base::Optional<LayoutUnit> baseline =
Base::InlineBlockBaselineOverride(line_direction))
return *baseline;
if (Base::PhysicalFragmentCount()) {
const NGPhysicalBoxFragment* fragment = Base::GetPhysicalFragment(0);
DCHECK(fragment);
if (base::Optional<LayoutUnit> offset = fragment->Baseline())
return *offset;
}
// This logic is in |LayoutBlock| and |LayoutBlockFlow|, but we cannot call
// |Base| because doing so may traverse |LayoutObject| tree, which may call
// this function for a child, but the child may be block fragmented.
if (!Base::ChildrenInline()) {
for (LayoutObject* child = Base::LastChild(); child;
child = child->PreviousSibling()) {
DCHECK(child->IsBox());
if (!child->IsFloatingOrOutOfFlowPositioned())
return LayoutUnit(-1);
}
}
return Base::EmptyLineBaseline(line_direction);
}
template <typename Base>
bool LayoutNGBlockFlowMixin<Base>::NodeAtPoint(
HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& accumulated_offset,
HitTestAction action) {
// When |this| is NG block fragmented, the painter should traverse fragemnts
// instead of |LayoutObject|, because this function cannot handle block
// fragmented objects. We can come here only when |this| cannot traverse
// fragments, or the parent is legacy.
DCHECK(!Base::CanTraversePhysicalFragments() ||
!Base::Parent()->CanTraversePhysicalFragments());
DCHECK_LE(Base::PhysicalFragmentCount(), 1u);
if (!Base::MayIntersect(result, hit_test_location, accumulated_offset))
return false;
if (Base::PhysicalFragmentCount()) {
const NGPhysicalBoxFragment* fragment = Base::GetPhysicalFragment(0);
DCHECK(fragment);
if (fragment->HasItems() ||
// Check descendants of this fragment because floats may be in the
// |NGFragmentItems| of the descendants.
(action == kHitTestFloat &&
fragment->HasFloatingDescendantsForPaint())) {
return NGBoxFragmentPainter(*fragment).NodeAtPoint(
result, hit_test_location, accumulated_offset, action);
}
}
return LayoutBlockFlow::NodeAtPoint(result, hit_test_location,
accumulated_offset, action);
}
template <typename Base>
PositionWithAffinity LayoutNGBlockFlowMixin<Base>::PositionForPoint(
const PhysicalOffset& point) const {
DCHECK_GE(Base::GetDocument().Lifecycle().GetState(),
DocumentLifecycle::kPrePaintClean);
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 (Base::PhysicalFragmentCount())
return Base::PositionForPointInFragments(point);
return Base::CreatePositionWithAffinity(0);
}
template <typename Base>
void LayoutNGBlockFlowMixin<Base>::DirtyLinesFromChangedChild(
LayoutObject* child,
MarkingBehavior marking_behavior) {
DCHECK_EQ(marking_behavior, kMarkContainerChain);
// We need to dirty line box fragments only if the child is once laid out in
// LayoutNG inline formatting context. New objects are handled in
// NGInlineNode::MarkLineBoxesDirty().
if (child->IsInLayoutNGInlineFormattingContext())
NGFragmentItems::DirtyLinesFromChangedChild(*child, *this);
}
template <typename Base>
void LayoutNGBlockFlowMixin<Base>::UpdateNGBlockLayout() {
LayoutAnalyzer::BlockScope analyzer(*this);
if (Base::IsOutOfFlowPositioned()) {
LayoutNGMixin<Base>::UpdateOutOfFlowBlockLayout();
return;
}
LayoutNGMixin<Base>::UpdateInFlowBlockLayout();
UpdateMargins();
}
template <typename Base>
void LayoutNGBlockFlowMixin<Base>::UpdateMargins() {
const LayoutBlock* containing_block = Base::ContainingBlock();
if (!containing_block || !containing_block->IsLayoutBlockFlow())
return;
// In the legacy engine, for regular block container layout, children
// calculate and store margins on themselves, while in NG that's done by the
// container. Since this object is a LayoutNG entry-point, we'll have to do it
// on ourselves, since that's what the legacy container expects.
const ComputedStyle& style = Base::StyleRef();
const ComputedStyle& cb_style = containing_block->StyleRef();
const auto writing_direction = cb_style.GetWritingDirection();
LayoutUnit available_logical_width =
LayoutBoxUtils::AvailableLogicalWidth(*this, containing_block);
NGBoxStrut margins = ComputePhysicalMargins(style, available_logical_width)
.ConvertToLogical(writing_direction);
ResolveInlineMargins(style, cb_style, available_logical_width,
Base::LogicalWidth(), &margins);
Base::SetMargin(margins.ConvertToPhysical(writing_direction));
}
template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutBlockFlow>;
template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutProgress>;
template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutRubyAsBlock>;
template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutRubyBase>;
template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutRubyRun>;
template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutRubyText>;
template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutTableCaption>;
template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutTableCell>;
} // namespace blink