| // Copyright 2016 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. |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_BLOCK_LAYOUT_ALGORITHM_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_BLOCK_LAYOUT_ALGORITHM_H_ |
| |
| #include "base/memory/scoped_refptr.h" |
| #include "base/optional.h" |
| #include "third_party/blink/renderer/core/core_export.h" |
| #include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h" |
| #include "third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h" |
| #include "third_party/blink/renderer/platform/geometry/layout_unit.h" |
| |
| namespace blink { |
| |
| enum class NGBreakStatus; |
| class NGConstraintSpace; |
| class NGEarlyBreak; |
| class NGFragment; |
| |
| // This struct is used for communicating to a child the position of the previous |
| // inflow child. This will be used to calculate the position of the next child. |
| struct NGPreviousInflowPosition { |
| LayoutUnit logical_block_offset; |
| NGMarginStrut margin_strut; |
| // > 0: Block-end annotation space of the previous line |
| // < 0: Block-end annotation overflow of the previous line |
| LayoutUnit block_end_annotation_space; |
| bool self_collapsing_child_had_clearance; |
| }; |
| |
| // This strut holds information for the current inflow child. The data is not |
| // useful outside of handling this single inflow child. |
| struct NGInflowChildData { |
| NGBfcOffset bfc_offset_estimate; |
| NGMarginStrut margin_strut; |
| NGBoxStrut margins; |
| bool margins_fully_resolved; |
| bool allow_discard_start_margin; |
| }; |
| |
| // A class for general block layout (e.g. a <div> with no special style). |
| // Lays out the children in sequence. |
| class CORE_EXPORT NGBlockLayoutAlgorithm |
| : public NGLayoutAlgorithm<NGBlockNode, |
| NGBoxFragmentBuilder, |
| NGBlockBreakToken> { |
| public: |
| // Default constructor. |
| NGBlockLayoutAlgorithm(const NGLayoutAlgorithmParams& params); |
| |
| ~NGBlockLayoutAlgorithm() override; |
| |
| void SetBoxType(NGPhysicalFragment::NGBoxType type); |
| |
| MinMaxSizesResult ComputeMinMaxSizes(const MinMaxSizesInput&) const override; |
| scoped_refptr<const NGLayoutResult> Layout() override; |
| |
| private: |
| NOINLINE scoped_refptr<const NGLayoutResult> |
| LayoutWithInlineChildLayoutContext(const NGLayoutInputNode& first_child); |
| NOINLINE scoped_refptr<const NGLayoutResult> LayoutWithItemsBuilder( |
| const NGInlineNode& first_child, |
| NGInlineChildLayoutContext* context); |
| |
| // Lay out again, this time with a predefined good breakpoint that we |
| // discovered in the first pass. This happens when we run out of space in a |
| // fragmentainer at an less-than-ideal location, due to breaking restrictions, |
| // such as orphans, widows, break-before:avoid or break-after:avoid. |
| NOINLINE scoped_refptr<const NGLayoutResult> RelayoutAndBreakEarlier( |
| const NGEarlyBreak&); |
| |
| NOINLINE scoped_refptr<const NGLayoutResult> RelayoutIgnoringLineClamp(); |
| |
| inline scoped_refptr<const NGLayoutResult> Layout( |
| NGInlineChildLayoutContext* inline_child_layout_context); |
| |
| scoped_refptr<const NGLayoutResult> FinishLayout(NGPreviousInflowPosition*, |
| NGInlineChildLayoutContext*); |
| |
| // Return the BFC block offset of this block. |
| LayoutUnit BfcBlockOffset() const { |
| // If we have resolved our BFC block offset, use that. |
| if (container_builder_.BfcBlockOffset()) |
| return *container_builder_.BfcBlockOffset(); |
| // Otherwise fall back to the BFC block offset assigned by the parent |
| // algorithm. |
| return ConstraintSpace().BfcOffset().block_offset; |
| } |
| |
| // Return the BFC block offset of the next block-start border edge (for some |
| // child) we'd get if we commit pending margins. |
| LayoutUnit NextBorderEdge( |
| const NGPreviousInflowPosition& previous_inflow_position) const { |
| return BfcBlockOffset() + previous_inflow_position.logical_block_offset + |
| previous_inflow_position.margin_strut.Sum(); |
| } |
| |
| NGBoxStrut CalculateMargins(NGLayoutInputNode child, |
| bool is_new_fc, |
| bool* margins_fully_resolved); |
| |
| // Creates a new constraint space for the current child. |
| NGConstraintSpace CreateConstraintSpaceForChild( |
| const NGLayoutInputNode child, |
| const NGInflowChildData& child_data, |
| const LogicalSize child_available_size, |
| bool is_new_fc, |
| const base::Optional<LayoutUnit> bfc_block_offset = base::nullopt, |
| bool has_clearance_past_adjoining_floats = false, |
| LayoutUnit block_start_annotation_space = LayoutUnit()); |
| |
| // @return Estimated BFC block offset for the "to be layout" child. |
| NGInflowChildData ComputeChildData(const NGPreviousInflowPosition&, |
| NGLayoutInputNode, |
| const NGBreakToken* child_break_token, |
| bool is_new_fc); |
| |
| NGPreviousInflowPosition ComputeInflowPosition( |
| const NGPreviousInflowPosition&, |
| const NGLayoutInputNode child, |
| const NGInflowChildData&, |
| const base::Optional<LayoutUnit>& child_bfc_block_offset, |
| const LogicalOffset&, |
| const NGLayoutResult&, |
| const NGFragment&, |
| bool self_collapsing_child_had_clearance); |
| |
| // Position an self-collapsing child using the parent BFC block-offset. |
| // The fragment doesn't know its offset, but we can still calculate its BFC |
| // position because the parent fragment's BFC is known. |
| // Example: |
| // BFC Offset is known here because of the padding. |
| // <div style="padding: 1px"> |
| // <div id="zero" style="margin: 1px"></div> |
| LayoutUnit PositionSelfCollapsingChildWithParentBfc( |
| const NGLayoutInputNode& child, |
| const NGConstraintSpace& child_space, |
| const NGInflowChildData& child_data, |
| const NGLayoutResult&) const; |
| |
| // Try to reuse part of cached fragments. When reusing is possible, this |
| // function adds part of cached fragments to |container_builder_|, update |
| // |break_token_| to continue layout from the last reused fragment, and |
| // returns |true|. Otherwise returns |false|. |
| bool TryReuseFragmentsFromCache( |
| NGInlineNode child, |
| NGPreviousInflowPosition*, |
| scoped_refptr<const NGInlineBreakToken>* inline_break_token_out); |
| |
| void HandleOutOfFlowPositioned(const NGPreviousInflowPosition&, NGBlockNode); |
| void HandleFloat(const NGPreviousInflowPosition&, |
| NGBlockNode, |
| const NGBlockBreakToken*); |
| |
| // This uses the NGLayoutOpporunityIterator to position the fragment. |
| // |
| // An element that establishes a new formatting context must not overlap the |
| // margin box of any floats within the current BFC. |
| // |
| // Example: |
| // <div id="container"> |
| // <div id="float"></div> |
| // <div id="new-fc" style="margin-top: 20px;"></div> |
| // </div> |
| // 1) If #new-fc is small enough to fit the available space right from #float |
| // then it will be placed there and we collapse its margin. |
| // 2) If #new-fc is too big then we need to clear its position and place it |
| // below #float ignoring its vertical margin. |
| // |
| // Returns false if we need to abort layout, because a previously unknown BFC |
| // block offset has now been resolved. |
| NGLayoutResult::EStatus HandleNewFormattingContext( |
| NGLayoutInputNode child, |
| const NGBreakToken* child_break_token, |
| NGPreviousInflowPosition*); |
| |
| // Performs the actual layout of a new formatting context. This may be called |
| // multiple times from HandleNewFormattingContext. |
| scoped_refptr<const NGLayoutResult> LayoutNewFormattingContext( |
| NGLayoutInputNode child, |
| const NGBreakToken* child_break_token, |
| const NGInflowChildData&, |
| NGBfcOffset origin_offset, |
| bool abort_if_cleared, |
| NGBfcOffset* out_child_bfc_offset); |
| |
| // Handle an in-flow child. |
| // Returns false if we need to abort layout, because a previously unknown BFC |
| // block offset has now been resolved. (Same as HandleNewFormattingContext). |
| NGLayoutResult::EStatus HandleInflow( |
| NGLayoutInputNode child, |
| const NGBreakToken* child_break_token, |
| NGPreviousInflowPosition*, |
| NGInlineChildLayoutContext*, |
| scoped_refptr<const NGInlineBreakToken>* previous_inline_break_token); |
| |
| NGLayoutResult::EStatus FinishInflow( |
| NGLayoutInputNode child, |
| const NGBreakToken* child_break_token, |
| const NGConstraintSpace&, |
| bool has_clearance_past_adjoining_floats, |
| scoped_refptr<const NGLayoutResult>, |
| NGInflowChildData*, |
| NGPreviousInflowPosition*, |
| NGInlineChildLayoutContext*, |
| scoped_refptr<const NGInlineBreakToken>* previous_inline_break_token); |
| |
| // Performs any final adjustments for table-cells. |
| void FinalizeForTableCell(LayoutUnit unconstrained_intrinsic_block_size); |
| |
| // Return the amount of block space available in the current fragmentainer |
| // for the node being laid out by this algorithm. |
| LayoutUnit FragmentainerSpaceAvailable() const; |
| |
| // Consume all remaining fragmentainer space. This happens when we decide to |
| // break before a child. |
| // |
| // https://www.w3.org/TR/css-break-3/#box-splitting |
| void ConsumeRemainingFragmentainerSpace(NGPreviousInflowPosition*); |
| |
| // Final adjustments before fragment creation. We need to prevent the fragment |
| // from crossing fragmentainer boundaries, and rather create a break token if |
| // we're out of space. As part of finalizing we may also discover that we need |
| // to abort layout, because we've run out of space at a less-than-ideal |
| // location. In this case, false will be returned. Otherwise, true will be |
| // returned. |
| bool FinalizeForFragmentation(); |
| |
| // Insert a fragmentainer break before the child if necessary. |
| // See |::blink::BreakBeforeChildIfNeeded()| for more documentation. |
| NGBreakStatus BreakBeforeChildIfNeeded(NGLayoutInputNode child, |
| const NGLayoutResult&, |
| NGPreviousInflowPosition*, |
| LayoutUnit bfc_block_offset, |
| bool has_container_separation); |
| |
| // Look for a better breakpoint (than we already have) between lines (i.e. a |
| // class B breakpoint), and store it. |
| void UpdateEarlyBreakBetweenLines(); |
| |
| // Propagates the baseline from the given |child| if needed. |
| void PropagateBaselineFromChild(const NGPhysicalContainerFragment& child, |
| LayoutUnit block_offset); |
| |
| // If still unresolved, resolve the fragment's BFC block offset. |
| // |
| // This includes applying clearance, so the |bfc_block_offset| passed won't |
| // be the final BFC block-offset, if it wasn't large enough to get past all |
| // relevant floats. The updated BFC block-offset can be read out with |
| // |ContainerBfcBlockOffset()|. |
| // |
| // If the |forced_bfc_block_offset| has a value, it will override the given |
| // |bfc_block_offset|. Typically this comes from the input constraints, when |
| // the current node has clearance past adjoining floats, or has a re-layout |
| // due to a child resolving the BFC block-offset. |
| // |
| // In addition to resolving our BFC block offset, this will also position |
| // pending floats, and update our in-flow layout state. |
| // |
| // Returns false if resolving the BFC block-offset resulted in needing to |
| // abort layout. It will always return true otherwise. If the BFC |
| // block-offset was already resolved, this method does nothing (and returns |
| // true). |
| bool ResolveBfcBlockOffset( |
| NGPreviousInflowPosition*, |
| LayoutUnit bfc_block_offset, |
| const base::Optional<LayoutUnit> forced_bfc_block_offset); |
| |
| // This passes in the |forced_bfc_block_offset| from the input constraints, |
| // which is almost always desired. |
| bool ResolveBfcBlockOffset(NGPreviousInflowPosition* previous_inflow_position, |
| LayoutUnit bfc_block_offset) { |
| return ResolveBfcBlockOffset(previous_inflow_position, bfc_block_offset, |
| ConstraintSpace().ForcedBfcBlockOffset()); |
| } |
| |
| // A very common way to resolve the BFC block offset is to simply commit the |
| // pending margin, so here's a convenience overload for that. |
| bool ResolveBfcBlockOffset( |
| NGPreviousInflowPosition* previous_inflow_position) { |
| return ResolveBfcBlockOffset(previous_inflow_position, |
| NextBorderEdge(*previous_inflow_position)); |
| } |
| |
| // Mark this fragment as modifying its incoming margin-strut if it hasn't |
| // resolved its BFC block-offset yet. |
| void SetSubtreeModifiedMarginStrutIfNeeded(const Length* margin = nullptr) { |
| if (container_builder_.BfcBlockOffset()) |
| return; |
| |
| if (margin && margin->IsZero()) |
| return; |
| |
| container_builder_.SetSubtreeModifiedMarginStrut(); |
| } |
| |
| // Return true if the BFC block offset has changed and this means that we |
| // need to abort layout. |
| bool NeedsAbortOnBfcBlockOffsetChange() const; |
| |
| // Positions a list marker for the specified block content. |
| // Return false if it aborts when resolving BFC block offset for LI. |
| bool PositionOrPropagateListMarker(const NGLayoutResult&, |
| LogicalOffset*, |
| NGPreviousInflowPosition*); |
| |
| // Positions a list marker when the block does not have any line boxes. |
| // Return false if it aborts when resolving BFC block offset for LI. |
| bool PositionListMarkerWithoutLineBoxes(NGPreviousInflowPosition*); |
| |
| // Calculates logical offset for the current fragment using either {@code |
| // intrinsic_block_size_} when the fragment doesn't know it's offset or |
| // {@code known_fragment_offset} if the fragment knows it's offset |
| // @return Fragment's offset relative to the fragment's parent. |
| LogicalOffset CalculateLogicalOffset( |
| const NGFragment& fragment, |
| LayoutUnit child_bfc_line_offset, |
| const base::Optional<LayoutUnit>& child_bfc_block_offset); |
| |
| // In quirks mode the body element will stretch to fit the viewport. |
| // |
| // In order to determine the final block-size we need to take the available |
| // block-size minus the total block-direction margin. |
| // |
| // This block-direction margin is non-trivial to calculate for the body |
| // element, and is computed upfront for the |ClampIntrinsicBlockSize| |
| // function. |
| base::Optional<LayoutUnit> CalculateQuirkyBodyMarginBlockSum( |
| const NGMarginStrut& end_margin_strut); |
| |
| // Returns true if |this| is a ruby segment (LayoutNGRubyRun) and the |
| // specified |child| is a ruby annotation box (LayoutNGRubyText). |
| bool IsRubyText(const NGLayoutInputNode& child) const; |
| |
| // Layout |ruby_text_child| content, and decide the location of |
| // |ruby_text_child|. This is called only if IsRubyText() returns true. |
| void HandleRubyText(NGBlockNode ruby_text_child); |
| |
| // Layout |placeholder| content, and decide the location of |placeholder|. |
| // This is called only if |this| is a text control. |
| void HandleTextControlPlaceholder( |
| NGBlockNode placeholder, |
| const NGPreviousInflowPosition& previous_inflow_position); |
| |
| // Adjusts the inline offset of the slider thumb box from the value of |
| // HTMLInputElement. |
| LogicalOffset AdjustSliderThumbInlineOffset( |
| const NGFragment& fragment, |
| const LogicalOffset& logical_offset); |
| |
| LogicalSize child_percentage_size_; |
| LogicalSize replaced_child_percentage_size_; |
| |
| scoped_refptr<const NGLayoutResult> previous_result_; |
| |
| // Intrinsic block size based on child layout and containment. |
| LayoutUnit intrinsic_block_size_; |
| |
| // The line box index at which we ran out of space. This where we'll actually |
| // end up breaking, unless we determine that we should break earlier in order |
| // to satisfy the widows request. |
| int first_overflowing_line_ = 0; |
| |
| // Set if we should fit as many lines as there's room for, i.e. no early |
| // break. In that case we'll break before first_overflowing_line_. In this |
| // case there'll either be enough widows for the next fragment, or we have |
| // determined that we're unable to fulfill the widows request. |
| bool fit_all_lines_ : 1; |
| |
| // Set if we're resuming layout of a node that has already produced fragments. |
| bool is_resuming_ : 1; |
| |
| // Set when we're to abort if the BFC block offset gets resolved or updated. |
| // Sometimes we walk past elements (i.e. floats) that depend on the BFC block |
| // offset being known (in order to position and lay themselves out properly). |
| // When this happens, and we finally manage to resolve (or update) the BFC |
| // block offset at some subsequent element, we need to check if this flag is |
| // set, and abort layout if it is. |
| bool abort_when_bfc_block_offset_updated_ : 1; |
| |
| // This will be set during block fragmentation once we've processed the first |
| // in-flow child of a container. It is used to check if we're at a valid class |
| // A or B breakpoint (between block-level siblings or line box siblings). |
| bool has_processed_first_child_ : 1; |
| |
| // If true, ignore the line-clamp property as truncation wont be required. |
| bool ignore_line_clamp_ : 1; |
| |
| // If this is within a -webkit-line-clamp context. |
| bool is_line_clamp_context_ : 1; |
| |
| // If set, this is the number of lines until a clamp. A value of 1 indicates |
| // the current line should be clamped. This may go negative. |
| base::Optional<int> lines_until_clamp_; |
| |
| NGExclusionSpace exclusion_space_; |
| |
| // If set, one of the lines was clamped and this is the intrinsic size at the |
| // time of the clamp. |
| base::Optional<LayoutUnit> intrinsic_block_size_when_clamped_; |
| |
| // When set, this will specify where to break before or inside. |
| const NGEarlyBreak* early_break_ = nullptr; |
| }; |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_BLOCK_LAYOUT_ALGORITHM_H_ |