Skip to content

Commit

Permalink
[LayoutNG] Add the "simplified" layout pass.
Browse files Browse the repository at this point in the history
This introduces the simplified layout algorithm. This is triggered when
 - An OOF descendant has its constraints changed.
 - The block-size of a fragment changes in size.

There are several nuanced details with this algorithm. E.g. determining
the correct static position for an OOF descendant.

Bug: 635619
Change-Id: Iba4c4b82edf873fa4cad9ba28e9fa3849a4a8e8e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1590237
Commit-Queue: Ian Kilpatrick <ikilpatrick@chromium.org>
Reviewed-by: Koji Ishii <kojii@chromium.org>
Reviewed-by: Morten Stenshorne <mstensho@chromium.org>
Cr-Commit-Position: refs/heads/master@{#659745}
  • Loading branch information
bfgeek authored and Commit Bot committed May 15, 2019
1 parent a69d6b2 commit 0987fcf
Show file tree
Hide file tree
Showing 38 changed files with 1,239 additions and 111 deletions.
4 changes: 4 additions & 0 deletions third_party/blink/renderer/core/layout/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ blink_core_sources("layout") {
"ng/ng_block_child_iterator.h",
"ng/ng_block_layout_algorithm.cc",
"ng/ng_block_layout_algorithm.h",
"ng/ng_block_layout_algorithm_utils.cc",
"ng/ng_block_layout_algorithm_utils.h",
"ng/ng_block_node.cc",
"ng/ng_block_node.h",
"ng/ng_box_fragment.cc",
Expand Down Expand Up @@ -468,6 +470,8 @@ blink_core_sources("layout") {
"ng/ng_positioned_float.h",
"ng/ng_relative_utils.cc",
"ng/ng_relative_utils.h",
"ng/ng_simplified_layout_algorithm.cc",
"ng/ng_simplified_layout_algorithm.h",
"ng/ng_space_utils.cc",
"ng/ng_space_utils.h",
"ng/ng_text_decoration_offset.cc",
Expand Down
66 changes: 57 additions & 9 deletions third_party/blink/renderer/core/layout/layout_box.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2316,25 +2316,46 @@ void LayoutBox::SetCachedLayoutResult(const NGLayoutResult& layout_result,
return;
if (layout_result.GetConstraintSpaceForCaching().IsIntermediateLayout())
return;
if (layout_result.PhysicalFragment().BreakToken())
return;

cached_layout_result_ = &layout_result;
}

scoped_refptr<const NGLayoutResult> LayoutBox::CachedLayoutResult(
const NGConstraintSpace& new_space,
const NGBreakToken* break_token,
base::Optional<NGFragmentGeometry>* initial_fragment_geometry) {
base::Optional<NGFragmentGeometry>* initial_fragment_geometry,
NGLayoutCacheStatus* out_cache_status) {
*out_cache_status = NGLayoutCacheStatus::kNeedsLayout;

if (!RuntimeEnabledFeatures::LayoutNGFragmentCachingEnabled())
return nullptr;

// TODO(cbiesinger): Support caching fragmented boxes.
if (break_token)
return nullptr;

// Set our initial temporary cache status to "hit".
NGLayoutCacheStatus cache_status = NGLayoutCacheStatus::kHit;

if (SelfNeedsLayoutForStyle() || NormalChildNeedsLayout() ||
PosChildNeedsLayout() || NeedsSimplifiedNormalFlowLayout() ||
(NeedsPositionedMovementLayout() && !NeedsPositionedMovementLayoutOnly()))
return nullptr;
(NeedsPositionedMovementLayout() &&
!NeedsPositionedMovementLayoutOnly())) {
// Check if we only need "simplified" layout. We don't abort yet, as we
// need to check if other things (like floats) will require us to perform a
// full layout.
//
// We don't regenerate any lineboxes during our "simplified" layout pass.
// If something needs "simplified" layout within a linebox, (e.g. an
// atomic-inline) we miss the cache.
if (NeedsSimplifiedLayoutOnly() &&
(!ChildrenInline() || !NeedsSimplifiedNormalFlowLayout()))
cache_status = NGLayoutCacheStatus::kNeedsSimplifiedLayout;
else
return nullptr;
}

const NGLayoutResult* cached_layout_result = GetCachedLayoutResult();
if (!cached_layout_result)
Expand All @@ -2343,6 +2364,8 @@ scoped_refptr<const NGLayoutResult> LayoutBox::CachedLayoutResult(
const NGPhysicalContainerFragment& physical_fragment =
cached_layout_result->PhysicalFragment();

DCHECK(!physical_fragment.BreakToken());

// If we have an orthogonal flow root descendant, we don't attempt to cache
// our layout result. This is because the initial containing block size may
// have changed, having a high likelihood of changing the size of the
Expand All @@ -2351,21 +2374,26 @@ scoped_refptr<const NGLayoutResult> LayoutBox::CachedLayoutResult(
return nullptr;

NGBlockNode node(this);
if (CalculateSizeBasedLayoutCacheStatus(
node, *cached_layout_result, new_space, initial_fragment_geometry) !=
NGLayoutCacheStatus::kHit)
NGLayoutCacheStatus size_cache_status = CalculateSizeBasedLayoutCacheStatus(
node, *cached_layout_result, new_space, initial_fragment_geometry);

// If our size may change (or we know a descendants size may change), we miss
// the cache.
if (size_cache_status == NGLayoutCacheStatus::kNeedsLayout)
return nullptr;

// Update our temporary cache status, if the size cache check indicated we
// might need simplified layout.
if (size_cache_status == NGLayoutCacheStatus::kNeedsSimplifiedLayout)
cache_status = NGLayoutCacheStatus::kNeedsSimplifiedLayout;

base::Optional<LayoutUnit> bfc_block_offset =
cached_layout_result->BfcBlockOffset();
LayoutUnit bfc_line_offset = new_space.BfcOffset().line_offset;

const NGConstraintSpace& old_space =
cached_layout_result->GetConstraintSpaceForCaching();

DCHECK_EQ(old_space.BfcOffset().line_offset,
cached_layout_result->BfcLineOffset());

// Check the BFC offset. Even if they don't match, there're some cases we can
// still reuse the fragment.
bool is_bfc_offset_equal = new_space.BfcOffset() == old_space.BfcOffset();
Expand Down Expand Up @@ -2394,11 +2422,31 @@ scoped_refptr<const NGLayoutResult> LayoutBox::CachedLayoutResult(
new_space.ClearanceOffset() != old_space.ClearanceOffset())) {
DCHECK(!CreatesNewFormattingContext());

// If we have a different BFC offset, or exclusion space we can't perform
// "simplified" layout.
// This may occur if our %-block-size has changed (allowing "simplified"
// layout), and we've been pushed down in the BFC coordinate space by a
// sibling.
// The "simplified" layout algorithm doesn't have the required logic to
// shift any added exclusions within the output exclusion space.
if (cache_status == NGLayoutCacheStatus::kNeedsSimplifiedLayout)
return nullptr;

DCHECK_EQ(cache_status, NGLayoutCacheStatus::kHit);

if (!MaySkipLayoutWithinBlockFormattingContext(
*cached_layout_result, new_space, &bfc_block_offset))
return nullptr;
}

// We've performed all of the cache checks at this point. If we need
// "simplified" layout then abort now.
*out_cache_status = cache_status;
if (*out_cache_status == NGLayoutCacheStatus::kNeedsSimplifiedLayout)
return nullptr;

DCHECK_EQ(*out_cache_status, NGLayoutCacheStatus::kHit);

// We can safely re-use this fragment if we are positioned, and only our
// position constraints changed (left/top/etc). However we need to clear the
// dirty layout bit(s).
Expand Down
6 changes: 5 additions & 1 deletion third_party/blink/renderer/core/layout/layout_box.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ShapeOutsideInfo;
struct BoxLayoutExtraInput;
class NGBreakToken;
struct NGFragmentGeometry;
enum class NGLayoutCacheStatus;
class NGLayoutResult;
struct NGPhysicalBoxStrut;
struct PaintInfo;
Expand Down Expand Up @@ -910,11 +911,14 @@ class CORE_EXPORT LayoutBox : public LayoutBoxModelObject {
// This method (while determining if the layout result can be reused), *may*
// calculate the |initial_fragment_geometry| of the node.
//
// |out_cache_status| indicates what type of layout pass is required.
//
// TODO(ikilpatrick): Move this function into NGBlockNode.
scoped_refptr<const NGLayoutResult> CachedLayoutResult(
const NGConstraintSpace&,
const NGBreakToken*,
base::Optional<NGFragmentGeometry>* initial_fragment_geometry);
base::Optional<NGFragmentGeometry>* initial_fragment_geometry,
NGLayoutCacheStatus* out_cache_status);

void SetSpannerPlaceholder(LayoutMultiColumnSpannerPlaceholder&);
void ClearSpannerPlaceholder();
Expand Down
11 changes: 11 additions & 0 deletions third_party/blink/renderer/core/layout/layout_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,17 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver,
!bitfields_.NeedsSimplifiedNormalFlowLayout();
}

bool NeedsSimplifiedLayoutOnly() const {
// We don't need to check |SelfNeedsLayoutForAvailableSpace| as an
// additional check will determine if we need to perform full layout based
// on the available space.
return (bitfields_.PosChildNeedsLayout() ||
bitfields_.NeedsSimplifiedNormalFlowLayout()) &&
!bitfields_.SelfNeedsLayoutForStyle() &&
!bitfields_.NormalChildNeedsLayout() &&
!bitfields_.NeedsPositionedMovementLayout();
}

bool SelfNeedsLayout() const {
return bitfields_.SelfNeedsLayoutForStyle() ||
bitfields_.SelfNeedsLayoutForAvailableSpace();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,16 @@ class CORE_EXPORT NGExclusionSpaceInternal {
return !(*this == other);
}

#if DCHECK_IS_ON()
void CheckSameForSimplifiedLayout(
const NGExclusionSpaceInternal& other) const {
DCHECK_EQ(num_exclusions_, other.num_exclusions_);
for (wtf_size_t i = 0; i < num_exclusions_; ++i) {
DCHECK(*exclusions_->data.at(i) == *other.exclusions_->data.at(i));
}
}
#endif

// This struct represents the side of a float against the "edge" of a shelf.
struct NGShelfEdge {
NGShelfEdge(LayoutUnit block_start, LayoutUnit block_end)
Expand Down Expand Up @@ -538,6 +548,14 @@ class CORE_EXPORT NGExclusionSpace {
return !(*this == other);
}

#if DCHECK_IS_ON()
void CheckSameForSimplifiedLayout(const NGExclusionSpace& other) const {
DCHECK_EQ((bool)exclusion_space_, (bool)other.exclusion_space_);
if (exclusion_space_)
exclusion_space_->CheckSameForSimplifiedLayout(*other.exclusion_space_);
}
#endif

private:
mutable std::unique_ptr<NGExclusionSpaceInternal> exclusion_space_;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ struct CORE_EXPORT NGPhysicalBoxStrut {
return LayoutRectOutsets(top, right, bottom, left);
}

bool operator==(const NGPhysicalBoxStrut& other) const {
return top == other.top && right == other.right && bottom == other.bottom &&
left == other.left;
}

LayoutUnit top;
LayoutUnit right;
LayoutUnit bottom;
Expand Down
11 changes: 11 additions & 0 deletions third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ class CORE_EXPORT NGBaselineList {

void emplace_back(NGBaselineRequest request, LayoutUnit offset);

#if DCHECK_IS_ON()
bool operator==(const NGBaselineList& other) const {
for (wtf_size_t i = 0; i < NGBaselineRequest::kTypeIdCount; ++i) {
if (offsets_[i] != other.offsets_[i])
return false;
}

return true;
}
#endif

class const_iterator {
public:
explicit const_iterator(unsigned type_id, const LayoutUnit* offset)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class CORE_EXPORT NGUnpositionedListMarker final {
NGBoxFragmentBuilder*) const;
LayoutUnit InlineOffset(const LayoutUnit marker_inline_size) const;

bool operator==(const NGUnpositionedListMarker& other) const {
return marker_layout_object_ == other.marker_layout_object_;
}

private:
bool IsImage() const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
Expand Down Expand Up @@ -784,48 +785,27 @@ const NGInlineBreakToken* NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache(
void NGBlockLayoutAlgorithm::HandleOutOfFlowPositioned(
const NGPreviousInflowPosition& previous_inflow_position,
NGBlockNode child) {
const ComputedStyle& child_style = child.Style();

LogicalOffset offset = {border_scrollbar_padding_.inline_start,
previous_inflow_position.logical_block_offset};
DCHECK(child.IsOutOfFlowPositioned());
LogicalOffset static_offset = {border_scrollbar_padding_.inline_start,
previous_inflow_position.logical_block_offset};

// We only include the margin strut in the OOF static-position if we know we
// aren't going to be a zero-block-size fragment.
if (container_builder_.BfcBlockOffset())
offset.block_offset += previous_inflow_position.margin_strut.Sum();

if (child_style.IsOriginalDisplayInlineType()) {
// OOF-positioned nodes which were initially inline-level, however are in a
// block-level context, pretend they are in an inline-level context. E.g.
// they avoid floats, and respect text-align.
const TextDirection direction = ConstraintSpace().Direction();
LayoutUnit child_origin_line_offset =
container_builder_.BfcLineOffset() +
border_scrollbar_padding_.LineLeft(direction);

// Find a layout opportunity, this is where we would have placed a
// zero-sized line.
NGLayoutOpportunity opportunity = exclusion_space_.FindLayoutOpportunity(
{child_origin_line_offset, BfcBlockOffset() + offset.block_offset},
child_available_size_.inline_size, /* minimum_size */ LogicalSize());

LayoutUnit child_line_offset = IsLtr(direction)
? opportunity.rect.LineStartOffset()
: opportunity.rect.LineEndOffset();

// Convert back to the logical coordinate system. As the conversion is on
// an OOF-positioned node, we pretent it has zero inline-size.
offset.inline_offset = LogicalFromBfcLineOffset(
child_line_offset, container_builder_.BfcLineOffset(),
/* child_inline_size */ LayoutUnit(),
container_builder_.Size().inline_size, direction);

// Adjust for text alignment, within the layout opportunity.
offset.inline_offset +=
InlineOffsetForTextAlign(Style(), opportunity.rect.InlineSize());
}

container_builder_.AddOutOfFlowChildCandidate(child, offset);
static_offset.block_offset += previous_inflow_position.margin_strut.Sum();

if (child.Style().IsOriginalDisplayInlineType()) {
NGBfcOffset origin_bfc_offset = {
ConstraintSpace().BfcOffset().line_offset +
border_scrollbar_padding_.LineLeft(Style().Direction()),
BfcBlockOffset() + static_offset.block_offset};

static_offset.inline_offset += CalculateOutOfFlowStaticInlineLevelOffset(
Style(), origin_bfc_offset, exclusion_space_,
child_available_size_.inline_size);
}

container_builder_.AddOutOfFlowChildCandidate(child, static_offset);
}

void NGBlockLayoutAlgorithm::HandleFloat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ class NGBlockLayoutAlgorithmTest : public NGBaseLayoutAlgorithmTest {
scoped_refptr<const NGLayoutResult> RunCachedLayoutResult(
const NGConstraintSpace& space,
const NGBlockNode& node) {
NGLayoutCacheStatus cache_status;
base::Optional<NGFragmentGeometry> initial_fragment_geometry;
return To<LayoutBlockFlow>(node.GetLayoutBox())
->CachedLayoutResult(space, nullptr, &initial_fragment_geometry);
->CachedLayoutResult(space, nullptr, &initial_fragment_geometry,
&cache_status);
}

String DumpFragmentTree(const NGPhysicalBoxFragment* fragment) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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/ng_block_layout_algorithm_utils.h"

#include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"

namespace blink {

LayoutUnit CalculateOutOfFlowStaticInlineLevelOffset(
const ComputedStyle& container_style,
const NGBfcOffset& origin_bfc_offset,
const NGExclusionSpace& exclusion_space,
LayoutUnit child_available_inline_size) {
const TextDirection direction = container_style.Direction();

// Find a layout opportunity, where we would have placed a zero-sized line.
NGLayoutOpportunity opportunity = exclusion_space.FindLayoutOpportunity(
origin_bfc_offset, child_available_inline_size,
/* minimum_size */ LogicalSize());

LayoutUnit child_line_offset = IsLtr(direction)
? opportunity.rect.LineStartOffset()
: opportunity.rect.LineEndOffset();

LayoutUnit relative_line_offset =
child_line_offset - origin_bfc_offset.line_offset;

// Convert back to the logical coordinate system. As the conversion is on an
// OOF-positioned node, we pretent it has zero inline-size.
LayoutUnit inline_offset =
IsLtr(direction) ? relative_line_offset
: child_available_inline_size - relative_line_offset;

// Adjust for text alignment, within the layout opportunity.
LayoutUnit line_offset = LineOffsetForTextAlign(
container_style.GetTextAlign(), direction, opportunity.rect.InlineSize());

if (IsLtr(direction))
inline_offset += line_offset;
else
inline_offset += opportunity.rect.InlineSize() - line_offset;

return inline_offset;
}

} // namespace blink
Loading

0 comments on commit 0987fcf

Please sign in to comment.