blob: 1fa490158907afe1b502b0ab24f5c6186dccab8d [file] [log] [blame]
// 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 "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"
namespace blink {
class NGConstraintSpace;
class NGEarlyBreak;
class NGFragment;
class NGPhysicalLineBoxFragment;
// 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;
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 is_resuming_after_break;
};
// 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);
base::Optional<MinMaxSize> ComputeMinMaxSize(
const MinMaxSizeInput&) const override;
scoped_refptr<const NGLayoutResult> Layout() override;
private:
NOINLINE scoped_refptr<const NGLayoutResult>
LayoutWithInlineChildLayoutContext();
NOINLINE scoped_refptr<const NGLayoutResult> LayoutWithItemsBuilder(
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&);
inline scoped_refptr<const NGLayoutResult> Layout(
NGInlineChildLayoutContext* inline_child_layout_context);
scoped_refptr<const NGLayoutResult> FinishLayout(NGPreviousInflowPosition*);
// 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);
void StopMarginCollapsing(EMarginCollapse collapse_value,
LayoutUnit this_margin,
LayoutUnit* logical_block_offset,
NGMarginStrut* margin_strut);
// 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> forced_bfc_block_offset = base::nullopt,
bool has_clearance_past_adjoining_floats = false);
// @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|.
const NGInlineBreakToken* TryReuseFragmentsFromCache(
NGInlineNode child,
NGPreviousInflowPosition*,
bool* abort_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);
// Return the amount of block space available in the current fragmentainer
// for the node being laid out by this algorithm.
LayoutUnit FragmentainerSpaceAvailable() const;
// Return true if the node being laid out by this fragmentainer has used all
// the available space in the current fragmentainer.
// |block_offset| is the border-edge relative block offset we want to check
// whether fits within the fragmentainer or not.
bool IsFragmentainerOutOfSpace(LayoutUnit block_offset) const;
// 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();
// Outcome of considering (and possibly attempting) breaking before a child.
enum BreakStatus {
// Continue layout. No break was inserted before the child (but there may be
// a break inside).
kContinueWithoutBreaking,
// A break was inserted before the child. Discard the child fragment and
// finish layout of the container. If there was a break inside the child, it
// will be discarded along with the child fragment.
kBrokeBefore,
// The child couldn't fit here, but no break was inserted before the child,
// as it was an unappealing place to break, and we have a better earlier
// breakpoint. We now need to abort the current layout, and go back and
// re-layout to said earlier breakpoint.
kNeedsEarlierBreak
};
// Insert a fragmentainer break before the child if necessary. In that case,
// the previous in-flow position will be updated, we'll return |kBrokeBefore|.
// If we don't break inside, we'll consider the appeal of doing so anyway (and
// store it as the most appealing break point so far if that's the case),
// since we might have to go back and break here. Return
// |kContinueWithoutBreaking| if we're to continue laying out. If
// |kNeedsEarlierBreak| is returned, it means that we ran out of space, but
// shouldn't break before the child, but rather abort layout, and re-layout to
// a previously found good breakpoint.
// If |has_container_separation| is true, it means that we're at a valid
// breakpoint. We obviously prefer valid breakpoints, but sometimes we need to
// break at undesirable locations. Class A breakpoints occur between block
// siblings. Class B breakpoints between line boxes. Both these breakpoint
// classes imply that we're already past the first in-flow child in the
// container, but there's also another way of achieving container separation:
// class C breakpoints. Those occur if there's a positive gap between the
// block-start content edge of the container and the block-start margin edge
// of the first in-flow child. This can happen when in-flow content is pushed
// down by floats. https://www.w3.org/TR/css-break-3/#possible-breaks
BreakStatus BreakBeforeChildIfNeeded(NGLayoutInputNode child,
const NGLayoutResult&,
NGPreviousInflowPosition*,
LayoutUnit block_offset,
bool has_container_separation);
// Insert either a soft or forced break before the child.
void BreakBeforeChild(NGLayoutInputNode child,
const NGLayoutResult&,
LayoutUnit block_offset,
NGBreakAppeal,
bool is_forced_break,
NGPreviousInflowPosition*);
void BreakBeforeChild(NGLayoutInputNode child,
NGBreakAppeal,
bool is_forced_break,
NGPreviousInflowPosition*);
// Propagate the minimal space shortage from a child.
void PropagateSpaceShortage(const NGLayoutResult&, LayoutUnit block_offset);
// Look for a better breakpoint (than we already have) either before the child
// (class A breakpoint), or inside it (class A or B breakpoint), and store it.
void UpdateEarlyBreakAtBlockChild(NGBlockNode child,
const NGLayoutResult& layout_result,
NGBreakAppeal break_before);
// Look for a better breakpoint (than we already have) between lines (i.e. a
// class B breakpoint), and store it.
void UpdateEarlyBreakBetweenLines();
void PropagateBaselinesFromChildren();
bool AddBaseline(const NGBaselineRequest&,
const NGPhysicalFragment&,
LayoutUnit child_offset);
// Compute the baseline offset of a line box from the content box.
// Line boxes are in line-relative coordinates. This function returns the
// offset in flow-relative coordinates.
LayoutUnit ComputeLineBoxBaselineOffset(
const NGBaselineRequest&,
const NGPhysicalLineBoxFragment&,
LayoutUnit line_box_block_offset) const;
// 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);
// Computes minimum size for HTML and BODY elements in quirks mode.
// Returns kIndefiniteSize in all other cases.
LayoutUnit CalculateMinimumBlockSize(const NGMarginStrut& end_margin_strut);
const NGBoxStrut border_padding_;
NGBoxStrut border_scrollbar_padding_;
LogicalSize child_available_size_;
LogicalSize child_percentage_size_;
LogicalSize replaced_child_percentage_size_;
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_ = false;
// Set if we're resuming layout of a node that has already produced fragments.
bool is_resuming_;
// 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_ = false;
// 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_ = false;
bool did_break_before_child_ = false;
NGExclusionSpace exclusion_space_;
// 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_