diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-07-31 15:50:41 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-08-30 12:35:23 +0000 |
commit | 7b2ffa587235a47d4094787d72f38102089f402a (patch) | |
tree | 30e82af9cbab08a7fa028bb18f4f2987a3f74dfa /chromium/third_party/blink/renderer/core/layout/ng | |
parent | d94af01c90575348c4e81a418257f254b6f8d225 (diff) | |
download | qtwebengine-chromium-7b2ffa587235a47d4094787d72f38102089f402a.tar.gz |
BASELINE: Update Chromium to 76.0.3809.94
Change-Id: I321c3f5f929c105aec0f98c5091ef6108822e647
Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/layout/ng')
168 files changed, 6956 insertions, 5802 deletions
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion.cc b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion.cc index 66d3f693cf8..d0d4e644037 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion.cc @@ -7,7 +7,8 @@ namespace blink { bool NGExclusion::operator==(const NGExclusion& other) const { - return std::tie(other.rect, other.type) == std::tie(rect, type); + return type == other.type && rect == other.rect && + shape_data == other.shape_data; } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.cc b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.cc index 592344b5576..549698b1fbe 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.cc @@ -16,29 +16,33 @@ namespace { // // We don't explicitly check the inline-size/block-size of the opportunity as // they are always produced in the order. -void InsertOpportunity(const NGLayoutOpportunity& opportunity, - Vector<NGLayoutOpportunity, 4>* opportunities) { - if (opportunities->IsEmpty()) { - opportunities->emplace_back(opportunity); +void InsertClosedArea( + const NGExclusionSpaceInternal::NGClosedArea area, + Vector<NGExclusionSpaceInternal::NGClosedArea, 4>* areas) { + if (areas->IsEmpty()) { + areas->emplace_back(area); return; } // We go backwards through the list as there is a higher probability that a - // new opportunity will be at the end of the list. - for (wtf_size_t j = opportunities->size() - 1; j >= 0; --j) { - const NGLayoutOpportunity& other = opportunities->at(j); - if (other.rect.BlockStartOffset() <= opportunity.rect.BlockStartOffset()) { + // new area will be at the end of the list. + for (wtf_size_t j = areas->size() - 1; j >= 0; --j) { + const NGExclusionSpaceInternal::NGClosedArea& other = areas->at(j); + if (other.opportunity.rect.BlockStartOffset() <= + area.opportunity.rect.BlockStartOffset()) { #if DCHECK_IS_ON() // If we have the same block-start offset ensure that the size of the // opportunity doesn't violate the order. - if (other.rect.BlockStartOffset() == - opportunity.rect.BlockStartOffset()) { - DCHECK_LE(other.rect.BlockSize(), opportunity.rect.BlockSize()); - DCHECK_GE(other.rect.InlineSize(), opportunity.rect.InlineSize()); + if (other.opportunity.rect.BlockStartOffset() == + area.opportunity.rect.BlockStartOffset()) { + DCHECK_LE(other.opportunity.rect.BlockSize(), + area.opportunity.rect.BlockSize()); + DCHECK_GE(other.opportunity.rect.InlineSize(), + area.opportunity.rect.InlineSize()); } #endif - opportunities->insert(j + 1, opportunity); + areas->insert(j + 1, area); return; } } @@ -172,7 +176,7 @@ NGLayoutOpportunity CreateLayoutOpportunity( } // namespace NGExclusionSpaceInternal::NGExclusionSpaceInternal() - : exclusions_(RefVector<scoped_refptr<const NGExclusion>>::Create()), + : exclusions_(base::MakeRefCounted<NGExclusionPtrArray>()), num_exclusions_(0), track_shape_exclusions_(false), derived_geometry_(nullptr) {} @@ -219,23 +223,21 @@ NGExclusionSpaceInternal::DerivedGeometry::DerivedGeometry( } void NGExclusionSpaceInternal::Add(scoped_refptr<const NGExclusion> exclusion) { - DCHECK_LE(num_exclusions_, exclusions_->size()); + DCHECK_LE(num_exclusions_, exclusions_->data.size()); bool already_exists = false; - if (num_exclusions_ < exclusions_->size()) { - if (*exclusion == *exclusions_->at(num_exclusions_) && - !exclusion->shape_data) { + if (num_exclusions_ < exclusions_->data.size()) { + if (*exclusion == *exclusions_->data.at(num_exclusions_)) { // We might be adding an exclusion seen in a previous layout pass. already_exists = true; } else { // Perform a copy-on-write if the number of exclusions has gone out of // sync. - scoped_refptr<RefVector<scoped_refptr<const NGExclusion>>> exclusions = - RefVector<scoped_refptr<const NGExclusion>>::Create(); - exclusions->GetMutableVector()->AppendRange( - exclusions_->GetVector().begin(), - exclusions_->GetVector().begin() + num_exclusions_); + scoped_refptr<NGExclusionPtrArray> exclusions = + base::MakeRefCounted<NGExclusionPtrArray>(); + exclusions->data.AppendRange(exclusions_->data.begin(), + exclusions_->data.begin() + num_exclusions_); std::swap(exclusions_, exclusions); } } @@ -261,7 +263,7 @@ void NGExclusionSpaceInternal::Add(scoped_refptr<const NGExclusion> exclusion) { std::max(last_float_block_start_, exclusion->rect.BlockStartOffset()); if (!already_exists) - exclusions_->emplace_back(std::move(exclusion)); + exclusions_->data.emplace_back(std::move(exclusion)); num_exclusions_++; } @@ -398,7 +400,9 @@ void NGExclusionSpaceInternal::DerivedGeometry::Add( *shelf.shape_exclusions)) : nullptr); - InsertOpportunity(opportunity, &opportunities_); + InsertClosedArea(NGClosedArea(opportunity, shelf.line_left_edges, + shelf.line_right_edges), + &areas_); } } @@ -532,7 +536,7 @@ NGLayoutOpportunity NGExclusionSpaceInternal::DerivedGeometry::FindLayoutOpportunity( const NGBfcOffset& offset, const LayoutUnit available_inline_size, - const NGLogicalSize& minimum_size) const { + const LogicalSize& minimum_size) const { // TODO(ikilpatrick): Determine what to do for a -ve available_inline_size. NGLayoutOpportunity return_opportunity; @@ -580,12 +584,12 @@ void NGExclusionSpaceInternal::DerivedGeometry::IterateAllLayoutOpportunities( const LayoutUnit available_inline_size, const LambdaFunc& lambda) const { auto* shelves_it = shelves_.begin(); - auto* opps_it = opportunities_.begin(); + auto* areas_it = areas_.begin(); auto* const shelves_end = shelves_.end(); - auto* const opps_end = opportunities_.end(); + auto* const areas_end = areas_.end(); - while (shelves_it != shelves_end || opps_it != opps_end) { + while (shelves_it != shelves_end || areas_it != areas_end) { // We should never exhaust the opportunities list before the shelves list, // as there is always an infinitely sized shelf at the very end. DCHECK_NE(shelves_it, shelves_end); @@ -596,22 +600,35 @@ void NGExclusionSpaceInternal::DerivedGeometry::IterateAllLayoutOpportunities( continue; } - if (opps_it != opps_end) { - const NGLayoutOpportunity& opportunity = *opps_it; + if (areas_it != areas_end) { + const NGClosedArea& area = *areas_it; - if (!Intersects(opportunity, offset, available_inline_size)) { - ++opps_it; + if (!Intersects(area.opportunity, offset, available_inline_size)) { + ++areas_it; continue; } - // We always prefer the closed-off opportunity, instead of the shelf + LayoutUnit block_start_offset = std::max( + area.opportunity.rect.BlockStartOffset(), offset.block_offset); + + // We always prefer the closed-off area opportunity, instead of the shelf // opportunity if they exist at the some offset. - if (opportunity.rect.BlockStartOffset() <= shelf.block_offset) { - if (lambda(CreateLayoutOpportunity(opportunity, offset, - available_inline_size))) - return; + if (block_start_offset <= + std::max(shelf.block_offset, offset.block_offset)) { + LayoutUnit block_end_offset = area.opportunity.rect.BlockEndOffset(); + + bool has_solid_edges = + HasSolidEdges(area.line_left_edges, block_start_offset, + block_end_offset) && + HasSolidEdges(area.line_right_edges, block_start_offset, + block_end_offset); + if (has_solid_edges) { + if (lambda(CreateLayoutOpportunity(area.opportunity, offset, + available_inline_size))) + return; + } - ++opps_it; + ++areas_it; continue; } } @@ -636,9 +653,9 @@ NGExclusionSpaceInternal::GetDerivedGeometry() const { if (!derived_geometry_) { derived_geometry_ = std::make_unique<DerivedGeometry>(track_shape_exclusions_); - DCHECK_LE(num_exclusions_, exclusions_->size()); + DCHECK_LE(num_exclusions_, exclusions_->data.size()); for (wtf_size_t i = 0; i < num_exclusions_; ++i) - derived_geometry_->Add(*exclusions_->GetVector()[i]); + derived_geometry_->Add(*exclusions_->data[i]); } return *derived_geometry_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h index 01a7022a8fd..0d05cbcdee2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h @@ -13,12 +13,13 @@ #include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h" #include "third_party/blink/renderer/platform/wtf/allocator.h" -#include "third_party/blink/renderer/platform/wtf/ref_vector.h" #include "third_party/blink/renderer/platform/wtf/vector.h" namespace blink { typedef Vector<NGLayoutOpportunity, 8> LayoutOpportunityVector; +typedef base::RefCountedData<WTF::Vector<scoped_refptr<const NGExclusion>>> + NGExclusionPtrArray; // This class is an implementation detail. For use of the exclusion space, // see NGExclusionSpace below. NGExclusionSpace was designed to be cheap @@ -39,7 +40,7 @@ class CORE_EXPORT NGExclusionSpaceInternal { NGLayoutOpportunity FindLayoutOpportunity( const NGBfcOffset& offset, const LayoutUnit available_inline_size, - const NGLogicalSize& minimum_size) const { + const LogicalSize& minimum_size) const { // If the area clears all floats, we can just return the layout opportunity // which matches the available space. if (offset.block_offset >= @@ -95,8 +96,8 @@ class CORE_EXPORT NGExclusionSpaceInternal { // Pre-initializes the exclusions vector to something used in a previous // layout pass, however keeps the number of exclusions as zero. void PreInitialize(const NGExclusionSpaceInternal& other) { - DCHECK_EQ(exclusions_->size(), 0u); - DCHECK_GT(other.exclusions_->size(), 0u); + DCHECK(exclusions_->data.IsEmpty()); + DCHECK_GT(other.exclusions_->data.size(), 0u); exclusions_ = other.exclusions_; } @@ -106,13 +107,14 @@ class CORE_EXPORT NGExclusionSpaceInternal { if (!other.derived_geometry_) return; + track_shape_exclusions_ = other.track_shape_exclusions_; derived_geometry_ = std::move(other.derived_geometry_); other.derived_geometry_ = nullptr; // Iterate through all the exclusions which were added by the layout, and // update the DerivedGeometry. for (wtf_size_t i = other.num_exclusions_; i < num_exclusions_; ++i) { - const NGExclusion& exclusion = *exclusions_->at(i); + const NGExclusion& exclusion = *exclusions_->data.at(i); // If we come across an exclusion with shape data, we opt-out of this // optimization. @@ -134,7 +136,8 @@ class CORE_EXPORT NGExclusionSpaceInternal { // layout result. for (wtf_size_t i = previous_input ? previous_input->num_exclusions_ : 0; i < previous_output.num_exclusions_; ++i) { - Add(previous_output.exclusions_->at(i)->CopyWithOffset(offset_delta)); + Add(previous_output.exclusions_->data.at(i)->CopyWithOffset( + offset_delta)); } } @@ -143,6 +146,20 @@ 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) { + const auto& exclusion = *exclusions_->data.at(i); + const auto& other_exclusion = *other.exclusions_->data.at(i); + DCHECK(exclusion.rect == other_exclusion.rect); + DCHECK_EQ(exclusion.type, other_exclusion.type); + DCHECK_EQ((bool)exclusion.shape_data, (bool)other_exclusion.shape_data); + } + } +#endif + // This struct represents the side of a float against the "edge" of a shelf. struct NGShelfEdge { NGShelfEdge(LayoutUnit block_start, LayoutUnit block_end) @@ -224,6 +241,49 @@ class CORE_EXPORT NGExclusionSpaceInternal { bool has_shape_exclusions; }; + // The closed-off area is an internal data-structure representing an area + // above a float. It contains a layout opportunity, and two vectors of + // |NGShelfEdge|. E.g. + // + // 0 1 2 3 4 5 6 7 8 + // 0 +---+. .+---+ + // |xxx|. .|xxx| + // 10 |xxx|. .|xxx| + // +---+. .+---+ + // 20 ........ + // +---+ + // 30 |xxx| + // |xxx| + // 40 +---+ + // + // In the above example the closed-off area is represented with the dotted + // line. + // + // It has the internal values of: + // { + // opportunity: { + // start_offset: {20, LayoutUnit::Min()}, + // end_offset: {65, 25}, + // } + // line_left_edges: [{0, 15}], + // line_right_edges: [{0, 15}], + // } + // + // Once a closed-off area has been created, it can never be changed due to + // the property that floats always align their block-start edges. + struct NGClosedArea { + NGClosedArea(NGLayoutOpportunity opportunity, + const Vector<NGShelfEdge, 1>& line_left_edges, + const Vector<NGShelfEdge, 1>& line_right_edges) + : opportunity(opportunity), + line_left_edges(line_left_edges), + line_right_edges(line_right_edges) {} + + const NGLayoutOpportunity opportunity; + const Vector<NGShelfEdge, 1> line_left_edges; + const Vector<NGShelfEdge, 1> line_right_edges; + }; + private: // In order to reduce the amount of Vector copies, instances of a // NGExclusionSpaceInternal can share the same exclusions_ Vector. See the @@ -234,7 +294,7 @@ class CORE_EXPORT NGExclusionSpaceInternal { // // num_exclusions_ is how many exclusions *this* instance of an exclusion // space has, which may differ to the number of exclusions in the Vector. - scoped_refptr<RefVector<scoped_refptr<const NGExclusion>>> exclusions_; + scoped_refptr<NGExclusionPtrArray> exclusions_; wtf_size_t num_exclusions_; // These members are used for keeping track of the "lowest" offset for each @@ -283,7 +343,7 @@ class CORE_EXPORT NGExclusionSpaceInternal { NGLayoutOpportunity FindLayoutOpportunity( const NGBfcOffset& offset, const LayoutUnit available_inline_size, - const NGLogicalSize& minimum_size) const; + const LogicalSize& minimum_size) const; LayoutOpportunityVector AllLayoutOpportunities( const NGBfcOffset& offset, @@ -294,40 +354,22 @@ class CORE_EXPORT NGExclusionSpaceInternal { const LayoutUnit available_inline_size, const LambdaFunc&) const; - // See NGShelf for a broad description of what shelves are. We always begin - // with one, which has the internal value of: + // See |NGShelf| for a broad description of what shelves are. We always + // begin with one, which has the internal value of: // { // block_offset: LayoutUnit::Min(), // line_left: LayoutUnit::Min(), // line_right: LayoutUnit::Max(), // } // - // The list of opportunities represent "closed-off" areas. E.g. - // - // 0 1 2 3 4 5 6 7 8 - // 0 +---+. .+---+ - // |xxx|. .|xxx| - // 10 |xxx|. .|xxx| - // +---+. .+---+ - // 20 ........ - // +---+ - // 30 |xxx| - // |xxx| - // 40 +---+ - // - // In the above example the opportunity is represented with the dotted line. - // It has the internal values of: - // { - // start_offset: {20, LayoutUnit::Min()}, - // end_offset: {65, 25}, - // } - // Once an opportunity has been created, it can never been changed due to - // the property that floats always align their block-start edges. - // - // We exploit this property by keeping this list of "closed-off" areas, and - // removing shelves to make insertion faster. Vector<NGShelf, 4> shelves_; - Vector<NGLayoutOpportunity, 4> opportunities_; + + // See |NGClosedArea| for a broad description of what closed-off areas are. + // + // Floats always align their block-start edges. We exploit this property by + // keeping a list of closed-off areas. Once a closed-off area has been + // created, it can never change. + Vector<NGClosedArea, 4> areas_; bool track_shape_exclusions_; }; @@ -378,7 +420,7 @@ class CORE_EXPORT NGExclusionSpace { NGLayoutOpportunity FindLayoutOpportunity( const NGBfcOffset& offset, const LayoutUnit available_inline_size, - const NGLogicalSize& minimum_size) const { + const LogicalSize& minimum_size) const { if (!exclusion_space_) { NGBfcOffset end_offset( offset.line_offset + available_inline_size.ClampNegativeToZero(), @@ -510,6 +552,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_; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h index 830ce4998ed..a54a2c4e1a0 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h @@ -60,6 +60,13 @@ struct CORE_EXPORT NGLineLayoutOpportunity { DCHECK_GE(float_line_right_offset, float_line_left_offset); return float_line_right_offset - float_line_left_offset; } + + bool IsEqualToAvailableFloatInlineSize(LayoutUnit inline_size) const { + DCHECK_GE(float_line_right_offset, float_line_left_offset); + // Compare |line_right| isntead of |inline_size| to avoid returning |false| + // when |line_left + inline_size| exceeds |LayoutUnit::Max| and clamped. + return float_line_left_offset + inline_size == float_line_right_offset; + } }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.cc index ac4f0548432..05270df8a34 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.cc @@ -8,15 +8,6 @@ namespace blink { -bool NGBfcOffset::operator==(const NGBfcOffset& other) const { - return std::tie(other.line_offset, other.block_offset) == - std::tie(line_offset, block_offset); -} - -bool NGBfcOffset::operator!=(const NGBfcOffset& other) const { - return !operator==(other); -} - String NGBfcOffset::ToString() const { return String::Format("%dx%d", line_offset.ToInt(), block_offset.ToInt()); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h index ded1830cab5..ee363f9357a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h @@ -24,7 +24,7 @@ struct CORE_EXPORT NGBfcDelta { // a block formatting context (BFC). BFCs are agnostic to text direction, and // uses line_offset instead of inline_offset. // -// Care must be taken when converting this to a NGLogicalOffset to respect the +// Care must be taken when converting this to a LogicalOffset to respect the // text direction. struct CORE_EXPORT NGBfcOffset { NGBfcOffset() = default; @@ -44,8 +44,12 @@ struct CORE_EXPORT NGBfcOffset { block_offset + delta.block_offset_delta}; } - bool operator==(const NGBfcOffset& other) const; - bool operator!=(const NGBfcOffset& other) const; + bool operator==(const NGBfcOffset& other) const { + return std::tie(other.line_offset, other.block_offset) == + std::tie(line_offset, block_offset); + } + + bool operator!=(const NGBfcOffset& other) const { return !operator==(other); } String ToString() const; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_rect.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_rect.cc deleted file mode 100644 index d470abab9ee..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_rect.cc +++ /dev/null @@ -1,13 +0,0 @@ -// 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/geometry/ng_bfc_rect.h" - -namespace blink { - -bool NGBfcRect::operator==(const NGBfcRect& other) const { - return start_offset == other.start_offset && end_offset == other.end_offset; -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_rect.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_rect.h index 03d6bb9a68e..eac77c3251f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_rect.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_rect.h @@ -6,8 +6,8 @@ #define NGBfcRect_h #include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h" namespace blink { @@ -39,7 +39,10 @@ struct CORE_EXPORT NGBfcRect { return end_offset.line_offset - start_offset.line_offset; } - bool operator==(const NGBfcRect& other) const; + bool operator==(const NGBfcRect& other) const { + return start_offset == other.start_offset && end_offset == other.end_offset; + } + bool operator!=(const NGBfcRect& other) const { return !(*this == other); } NGBfcOffset start_offset; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.cc index 74607dc041f..4095506fb3a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.cc @@ -41,10 +41,6 @@ NGLineBoxStrut::NGLineBoxStrut(const NGBoxStrut& flow_relative, } } -LayoutRectOutsets NGPhysicalBoxStrut::ToLayoutRectOutsets() const { - return LayoutRectOutsets(top, right, bottom, left); -} - std::ostream& operator<<(std::ostream& stream, const NGLineBoxStrut& value) { return stream << "Inline: (" << value.inline_start << " " << value.inline_end << ") Line: (" << value.line_over << " " << value.line_under diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h index 78b342adff8..e4e8a9ed325 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h @@ -8,14 +8,14 @@ #include <utility> #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h" +#include "third_party/blink/renderer/platform/geometry/layout_rect_outsets.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h" #include "third_party/blink/renderer/platform/text/text_direction.h" #include "third_party/blink/renderer/platform/text/writing_mode.h" namespace blink { -class LayoutRectOutsets; struct NGLineBoxStrut; struct NGPhysicalBoxStrut; @@ -43,7 +43,7 @@ struct CORE_EXPORT NGBoxStrut { LayoutUnit InlineSum() const { return inline_start + inline_end; } LayoutUnit BlockSum() const { return block_start + block_end; } - NGLogicalOffset StartOffset() const { return {inline_start, block_start}; } + LogicalOffset StartOffset() const { return {inline_start, block_start}; } bool IsEmpty() const { return *this == NGBoxStrut(); } @@ -195,7 +195,16 @@ struct CORE_EXPORT NGPhysicalBoxStrut { LayoutUnit HorizontalSum() const { return left + right; } LayoutUnit VerticalSum() const { return top + bottom; } - LayoutRectOutsets ToLayoutRectOutsets() const; + LayoutRectOutsets ToLayoutRectOutsets() const { + 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; + } + + bool IsZero() const { return !top && !right && !bottom && !left; } LayoutUnit top; LayoutUnit right; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h new file mode 100644 index 00000000000..0c766c6279e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h @@ -0,0 +1,28 @@ +// 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GEOMETRY_NG_FRAGMENT_GEOMETRY_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GEOMETRY_NG_FRAGMENT_GEOMETRY_H_ + +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" +#include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" + +namespace blink { + +// This represents the initial (pre-layout) geometry of a fragment. E.g. +// - The inline-size of the fragment. +// - The block-size of the fragment (might be |kIndefiniteSize| if height is +// 'auto' for example). +// - The border, scrollbar, and padding. +// This *doesn't* necessarily represent the final geometry of the fragment. +struct NGFragmentGeometry { + LogicalSize border_box_size; + NGBoxStrut border; + NGBoxStrut scrollbar; + NGBoxStrut padding; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GEOMETRY_NG_FRAGMENT_GEOMETRY_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.cc deleted file mode 100644 index 8fead03022a..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.cc +++ /dev/null @@ -1,123 +0,0 @@ -// 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. - -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h" - -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" -#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" - -namespace blink { - -NGPhysicalOffset NGLogicalOffset::ConvertToPhysical( - WritingMode mode, - TextDirection direction, - NGPhysicalSize outer_size, - NGPhysicalSize inner_size) const { - switch (mode) { - case WritingMode::kHorizontalTb: - if (direction == TextDirection::kLtr) - return NGPhysicalOffset(inline_offset, block_offset); - else - return NGPhysicalOffset( - outer_size.width - inline_offset - inner_size.width, block_offset); - case WritingMode::kVerticalRl: - case WritingMode::kSidewaysRl: - if (direction == TextDirection::kLtr) - return NGPhysicalOffset( - outer_size.width - block_offset - inner_size.width, inline_offset); - else - return NGPhysicalOffset( - outer_size.width - block_offset - inner_size.width, - outer_size.height - inline_offset - inner_size.height); - case WritingMode::kVerticalLr: - if (direction == TextDirection::kLtr) - return NGPhysicalOffset(block_offset, inline_offset); - else - return NGPhysicalOffset( - block_offset, - outer_size.height - inline_offset - inner_size.height); - case WritingMode::kSidewaysLr: - if (direction == TextDirection::kLtr) - return NGPhysicalOffset( - block_offset, - outer_size.height - inline_offset - inner_size.height); - else - return NGPhysicalOffset(block_offset, inline_offset); - default: - NOTREACHED(); - return NGPhysicalOffset(); - } -} - -bool NGLogicalOffset::operator==(const NGLogicalOffset& other) const { - return std::tie(other.inline_offset, other.block_offset) == - std::tie(inline_offset, block_offset); -} - -bool NGLogicalOffset::operator!=(const NGLogicalOffset& other) const { - return !operator==(other); -} - -NGLogicalOffset NGLogicalOffset::operator+(const NGLogicalOffset& other) const { - NGLogicalOffset result; - result.inline_offset = this->inline_offset + other.inline_offset; - result.block_offset = this->block_offset + other.block_offset; - return result; -} - -NGLogicalOffset NGLogicalOffset::operator+(const NGLogicalSize& size) const { - return {inline_offset + size.inline_size, block_offset + size.block_size}; -} - -NGLogicalOffset& NGLogicalOffset::operator+=(const NGLogicalOffset& other) { - *this = *this + other; - return *this; -} - -NGLogicalOffset& NGLogicalOffset::operator+=(const NGLogicalSize& size) { - *this = *this + size; - return *this; -} - -bool NGLogicalOffset::operator>(const NGLogicalOffset& other) const { - return inline_offset > other.inline_offset && - block_offset > other.block_offset; -} - -bool NGLogicalOffset::operator>=(const NGLogicalOffset& other) const { - return inline_offset >= other.inline_offset && - block_offset >= other.block_offset; -} - -bool NGLogicalOffset::operator<(const NGLogicalOffset& other) const { - return inline_offset < other.inline_offset && - block_offset < other.block_offset; -} - -bool NGLogicalOffset::operator<=(const NGLogicalOffset& other) const { - return inline_offset <= other.inline_offset && - block_offset <= other.block_offset; -} - -NGLogicalDelta NGLogicalOffset::operator-(const NGLogicalOffset& other) const { - return {inline_offset - other.inline_offset, - block_offset - other.block_offset}; -} - -NGLogicalOffset& NGLogicalOffset::operator-=(const NGLogicalOffset& other) { - *this = *this - other; - return *this; -} - -String NGLogicalOffset::ToString() const { - return String::Format("%d,%d", inline_offset.ToInt(), block_offset.ToInt()); -} - -std::ostream& operator<<(std::ostream& os, const NGLogicalOffset& value) { - return os << value.ToString(); -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h deleted file mode 100644 index 1f3b38af731..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h +++ /dev/null @@ -1,66 +0,0 @@ -// 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 NGLogicalOffset_h -#define NGLogicalOffset_h - -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/platform/geometry/layout_unit.h" -#include "third_party/blink/renderer/platform/text/text_direction.h" -#include "third_party/blink/renderer/platform/text/writing_mode.h" - -namespace blink { - -struct NGLogicalDelta; -struct NGLogicalSize; -struct NGPhysicalOffset; -struct NGPhysicalSize; - -// NGLogicalOffset is the position of a rect (typically a fragment) relative to -// its parent rect in the logical coordinate system. -struct CORE_EXPORT NGLogicalOffset { - NGLogicalOffset() = default; - NGLogicalOffset(LayoutUnit inline_offset, LayoutUnit block_offset) - : inline_offset(inline_offset), block_offset(block_offset) {} - - LayoutUnit inline_offset; - LayoutUnit block_offset; - - // Converts a logical offset to a physical offset. See: - // https://drafts.csswg.org/css-writing-modes-3/#logical-to-physical - // PhysicalOffset will be the physical top left point of the rectangle - // described by offset + inner_size. Setting inner_size to 0,0 will return - // the same point. - // @param outer_size the size of the rect (typically a fragment). - // @param inner_size the size of the inner rect (typically a child fragment). - NGPhysicalOffset ConvertToPhysical(WritingMode, - TextDirection, - NGPhysicalSize outer_size, - NGPhysicalSize inner_size) const; - - bool operator==(const NGLogicalOffset& other) const; - bool operator!=(const NGLogicalOffset& other) const; - - NGLogicalOffset operator+(const NGLogicalOffset& other) const; - NGLogicalOffset operator+(const NGLogicalSize& size) const; - NGLogicalOffset& operator+=(const NGLogicalOffset& other); - NGLogicalOffset& operator+=(const NGLogicalSize& size); - - NGLogicalDelta operator-(const NGLogicalOffset& other) const; - NGLogicalOffset& operator-=(const NGLogicalOffset& other); - - bool operator>(const NGLogicalOffset& other) const; - bool operator>=(const NGLogicalOffset& other) const; - - bool operator<(const NGLogicalOffset& other) const; - bool operator<=(const NGLogicalOffset& other) const; - - String ToString() const; -}; - -CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGLogicalOffset&); - -} // namespace blink - -#endif // NGLogicalOffset_h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset_test.cc deleted file mode 100644 index 66fd969b333..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset_test.cc +++ /dev/null @@ -1,74 +0,0 @@ -// 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. - -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" - -#include "testing/gtest/include/gtest/gtest.h" - -namespace blink { - -namespace { - -TEST(NGGeometryUnitsTest, ConvertLogicalOffsetToPhysicalOffset) { - NGLogicalOffset logical_offset(LayoutUnit(20), LayoutUnit(30)); - NGPhysicalSize outer_size(LayoutUnit(300), LayoutUnit(400)); - NGPhysicalSize inner_size(LayoutUnit(5), LayoutUnit(65)); - NGPhysicalOffset offset; - - offset = logical_offset.ConvertToPhysical( - WritingMode::kHorizontalTb, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(20), offset.left); - EXPECT_EQ(LayoutUnit(30), offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kHorizontalTb, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(275), offset.left); - EXPECT_EQ(LayoutUnit(30), offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kVerticalRl, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(265), offset.left); - EXPECT_EQ(LayoutUnit(20), offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kVerticalRl, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(265), offset.left); - EXPECT_EQ(LayoutUnit(315), offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kSidewaysRl, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(265), offset.left); - EXPECT_EQ(LayoutUnit(20), offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kSidewaysRl, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(265), offset.left); - EXPECT_EQ(LayoutUnit(315), offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kVerticalLr, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(30), offset.left); - EXPECT_EQ(LayoutUnit(20), offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kVerticalLr, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(30), offset.left); - EXPECT_EQ(LayoutUnit(315), offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kSidewaysLr, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(30), offset.left); - EXPECT_EQ(LayoutUnit(315), offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kSidewaysLr, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(30), offset.left); - EXPECT_EQ(LayoutUnit(20), offset.top); -} - -} // namespace - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect.cc deleted file mode 100644 index 0c3b40cc708..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect.cc +++ /dev/null @@ -1,69 +0,0 @@ -// 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/geometry/ng_logical_rect.h" - -#include <algorithm> -#include "third_party/blink/renderer/platform/geometry/layout_rect.h" -#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" - -namespace blink { - -namespace { - -inline NGLogicalOffset Min(NGLogicalOffset a, NGLogicalOffset b) { - return {std::min(a.inline_offset, b.inline_offset), - std::min(a.block_offset, b.block_offset)}; -} - -inline NGLogicalOffset Max(NGLogicalOffset a, NGLogicalOffset b) { - return {std::max(a.inline_offset, b.inline_offset), - std::max(a.block_offset, b.block_offset)}; -} - -} // namespace - -NGLogicalRect::NGLogicalRect(const LayoutRect& source) - : NGLogicalRect({source.X(), source.Y()}, - {source.Width(), source.Height()}) {} - -LayoutRect NGLogicalRect::ToLayoutRect() const { - return {offset.inline_offset, offset.block_offset, size.inline_size, - size.block_size}; -} - -bool NGLogicalRect::operator==(const NGLogicalRect& other) const { - return other.offset == offset && other.size == size; -} - -NGLogicalRect NGLogicalRect::operator+(const NGLogicalOffset& offset) const { - return {this->offset + offset, size}; -} - -void NGLogicalRect::Unite(const NGLogicalRect& other) { - if (other.IsEmpty()) - return; - if (IsEmpty()) { - *this = other; - return; - } - - NGLogicalOffset new_end_offset(Max(EndOffset(), other.EndOffset())); - offset = Min(offset, other.offset); - size = new_end_offset - offset; -} - -String NGLogicalRect::ToString() const { - return String::Format("%s,%s %sx%s", - offset.inline_offset.ToString().Ascii().data(), - offset.block_offset.ToString().Ascii().data(), - size.inline_size.ToString().Ascii().data(), - size.block_size.ToString().Ascii().data()); -} - -std::ostream& operator<<(std::ostream& os, const NGLogicalRect& value) { - return os << value.ToString(); -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect.h deleted file mode 100644 index 114b7212b4e..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect.h +++ /dev/null @@ -1,45 +0,0 @@ -// 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. - -#ifndef NGLogicalRect_h -#define NGLogicalRect_h - -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" - -namespace blink { - -class LayoutRect; - -// NGLogicalRect is the position and size of a rect (typically a fragment) -// relative to the parent. -struct CORE_EXPORT NGLogicalRect { - NGLogicalRect() = default; - NGLogicalRect(const NGLogicalOffset& offset, const NGLogicalSize& size) - : offset(offset), size(size) {} - - explicit NGLogicalRect(const LayoutRect&); - LayoutRect ToLayoutRect() const; - - NGLogicalOffset offset; - NGLogicalSize size; - - NGLogicalOffset EndOffset() const { return offset + size; } - bool IsEmpty() const { return size.IsEmpty(); } - - bool operator==(const NGLogicalRect& other) const; - - NGLogicalRect operator+(const NGLogicalOffset&) const; - - void Unite(const NGLogicalRect&); - - String ToString() const; -}; - -CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGLogicalRect&); - -} // namespace blink - -#endif // NGLogicalRect_h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect_test.cc deleted file mode 100644 index b4a4917634c..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect_test.cc +++ /dev/null @@ -1,60 +0,0 @@ -// 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/geometry/ng_logical_rect.h" - -#include "testing/gtest/include/gtest/gtest.h" - -namespace blink { - -namespace { - -struct LogicalRectUniteTestData { - const char* test_case; - NGLogicalRect a; - NGLogicalRect b; - NGLogicalRect expected; -} logical_rect_unite_test_data[] = { - {"empty", {}, {}, {}}, - {"a empty", - {}, - {{LayoutUnit(1), LayoutUnit(2)}, {LayoutUnit(3), LayoutUnit(4)}}, - {{LayoutUnit(1), LayoutUnit(2)}, {LayoutUnit(3), LayoutUnit(4)}}}, - {"b empty", - {{LayoutUnit(1), LayoutUnit(2)}, {LayoutUnit(3), LayoutUnit(4)}}, - {}, - {{LayoutUnit(1), LayoutUnit(2)}, {LayoutUnit(3), LayoutUnit(4)}}}, - {"a larger", - {{LayoutUnit(100), LayoutUnit(50)}, {LayoutUnit(300), LayoutUnit(200)}}, - {{LayoutUnit(200), LayoutUnit(50)}, {LayoutUnit(200), LayoutUnit(200)}}, - {{LayoutUnit(100), LayoutUnit(50)}, {LayoutUnit(300), LayoutUnit(200)}}}, - {"b larger", - {{LayoutUnit(200), LayoutUnit(50)}, {LayoutUnit(200), LayoutUnit(200)}}, - {{LayoutUnit(100), LayoutUnit(50)}, {LayoutUnit(300), LayoutUnit(200)}}, - {{LayoutUnit(100), LayoutUnit(50)}, {LayoutUnit(300), LayoutUnit(200)}}}, -}; - -std::ostream& operator<<(std::ostream& os, - const LogicalRectUniteTestData& data) { - return os << "Unite " << data.test_case; -} - -class NGLogicalRectUniteTest - : public testing::Test, - public testing::WithParamInterface<LogicalRectUniteTestData> {}; - -INSTANTIATE_TEST_SUITE_P(NGGeometryUnitsTest, - NGLogicalRectUniteTest, - testing::ValuesIn(logical_rect_unite_test_data)); - -TEST_P(NGLogicalRectUniteTest, Data) { - const auto& data = GetParam(); - NGLogicalRect actual = data.a; - actual.Unite(data.b); - EXPECT_EQ(data.expected, actual); -} - -} // namespace - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.cc deleted file mode 100644 index ed4c4fac767..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.cc +++ /dev/null @@ -1,15 +0,0 @@ -// 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. - -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" - -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" - -namespace blink { - -std::ostream& operator<<(std::ostream& stream, const NGLogicalSize& value) { - return stream << value.inline_size << "x" << value.block_size; -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h deleted file mode 100644 index 344170e609e..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h +++ /dev/null @@ -1,70 +0,0 @@ -// 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 NGLogicalSize_h -#define NGLogicalSize_h - -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h" -#include "third_party/blink/renderer/platform/geometry/layout_unit.h" -#include "third_party/blink/renderer/platform/text/writing_mode.h" - -namespace blink { - -struct NGLogicalOffset; -#define NGSizeIndefinite LayoutUnit(-1) - -// NGLogicalSize is the size of rect (typically a fragment) in the logical -// coordinate system. -struct CORE_EXPORT NGLogicalSize { - NGLogicalSize() = default; - NGLogicalSize(LayoutUnit inline_size, LayoutUnit block_size) - : inline_size(inline_size), block_size(block_size) {} - - // Use ToNGPhysicalSize to convert to a physical size. - - LayoutUnit inline_size; - LayoutUnit block_size; - - bool operator==(const NGLogicalSize& other) const { - return std::tie(other.inline_size, other.block_size) == - std::tie(inline_size, block_size); - } - bool operator!=(const NGLogicalSize& other) const { - return !(*this == other); - } - - bool IsEmpty() const { - return inline_size == LayoutUnit() || block_size == LayoutUnit(); - } - - void Flip() { std::swap(inline_size, block_size); } -}; - -inline NGLogicalSize& operator-=(NGLogicalSize& a, const NGBoxStrut& b) { - a.inline_size -= b.InlineSum(); - a.block_size -= b.BlockSum(); - return a; -} - -CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGLogicalSize&); - -// NGLogicalDelta resolves the ambiguity of subtractions. -// -// "offset - offset" is ambiguous because both of below are true: -// offset + offset = offset -// offset + size = offset -// -// NGLogicalDelta resolves this ambiguity by allowing implicit conversions both -// to NGLogicalOffset and to NGLogicalSize. -struct CORE_EXPORT NGLogicalDelta : public NGLogicalSize { - public: - using NGLogicalSize::NGLogicalSize; - operator NGLogicalOffset() const { return {inline_size, block_size}; } -}; - -} // namespace blink - -#endif // NGLogicalSize_h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.cc deleted file mode 100644 index 9dc7146f5f5..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.cc +++ /dev/null @@ -1,59 +0,0 @@ -// 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. - -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" - -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" -#include "third_party/blink/renderer/platform/geometry/layout_point.h" -#include "third_party/blink/renderer/platform/geometry/layout_size.h" -#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" - -namespace blink { - -NGLogicalOffset NGPhysicalOffset::ConvertToLogical( - WritingMode mode, - TextDirection direction, - NGPhysicalSize outer_size, - NGPhysicalSize inner_size) const { - switch (mode) { - case WritingMode::kHorizontalTb: - if (direction == TextDirection::kLtr) - return NGLogicalOffset(left, top); - else - return NGLogicalOffset(outer_size.width - left - inner_size.width, top); - case WritingMode::kVerticalRl: - case WritingMode::kSidewaysRl: - if (direction == TextDirection::kLtr) - return NGLogicalOffset(top, outer_size.width - left - inner_size.width); - else - return NGLogicalOffset(outer_size.height - top - inner_size.height, - outer_size.width - left - inner_size.width); - case WritingMode::kVerticalLr: - if (direction == TextDirection::kLtr) - return NGLogicalOffset(top, left); - else - return NGLogicalOffset(outer_size.height - top - inner_size.height, - left); - case WritingMode::kSidewaysLr: - if (direction == TextDirection::kLtr) - return NGLogicalOffset(outer_size.height - top - inner_size.height, - left); - else - return NGLogicalOffset(top, left); - default: - NOTREACHED(); - return NGLogicalOffset(); - } -} - -String NGPhysicalOffset::ToString() const { - return String::Format("%d,%d", left.ToInt(), top.ToInt()); -} - -std::ostream& operator<<(std::ostream& os, const NGPhysicalOffset& value) { - return os << value.ToString(); -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h deleted file mode 100644 index d08b3aa246a..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h +++ /dev/null @@ -1,87 +0,0 @@ -// 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 NGPhysicalOffset_h -#define NGPhysicalOffset_h - -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/platform/geometry/layout_point.h" -#include "third_party/blink/renderer/platform/geometry/layout_size.h" -#include "third_party/blink/renderer/platform/geometry/layout_unit.h" -#include "third_party/blink/renderer/platform/text/text_direction.h" -#include "third_party/blink/renderer/platform/text/writing_mode.h" - -namespace blink { - -class LayoutPoint; -class LayoutSize; -struct NGLogicalOffset; -struct NGPhysicalSize; - -// NGPhysicalOffset is the position of a rect (typically a fragment) relative to -// its parent rect in the physical coordinate system. -struct CORE_EXPORT NGPhysicalOffset { - NGPhysicalOffset() = default; - NGPhysicalOffset(LayoutUnit left, LayoutUnit top) : left(left), top(top) {} - - LayoutUnit left; - LayoutUnit top; - - // Converts a physical offset to a logical offset. See: - // https://drafts.csswg.org/css-writing-modes-3/#logical-to-physical - // @param outer_size the size of the rect (typically a fragment). - // @param inner_size the size of the inner rect (typically a child fragment). - NGLogicalOffset ConvertToLogical(WritingMode, - TextDirection, - NGPhysicalSize outer_size, - NGPhysicalSize inner_size) const; - - NGPhysicalOffset operator+(const NGPhysicalOffset& other) const { - return NGPhysicalOffset{this->left + other.left, this->top + other.top}; - } - NGPhysicalOffset& operator+=(const NGPhysicalOffset& other) { - *this = *this + other; - return *this; - } - - NGPhysicalOffset operator-(const NGPhysicalOffset& other) const { - return NGPhysicalOffset{this->left - other.left, this->top - other.top}; - } - NGPhysicalOffset& operator-=(const NGPhysicalOffset& other) { - *this = *this - other; - return *this; - } - - bool operator==(const NGPhysicalOffset& other) const { - return other.left == left && other.top == top; - } - - bool operator!=(const NGPhysicalOffset& other) const { - return !(*this == other); - } - - // Conversions from/to existing code. New code prefers type safety for - // logical/physical distinctions. - explicit NGPhysicalOffset(const LayoutPoint& point) { - left = point.X(); - top = point.Y(); - } - explicit NGPhysicalOffset(const LayoutSize& size) { - left = size.Width(); - top = size.Height(); - } - - // Conversions from/to existing code. New code prefers type safety for - // logical/physical distinctions. - LayoutPoint ToLayoutPoint() const { return {left, top}; } - LayoutSize ToLayoutSize() const { return {left, top}; } - - String ToString() const; -}; - -CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGPhysicalOffset&); - -} // namespace blink - -#endif // NGPhysicalOffset_h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.cc deleted file mode 100644 index 0b5ff293af7..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.cc +++ /dev/null @@ -1,119 +0,0 @@ -// 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/geometry/ng_physical_offset_rect.h" - -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" -#include "third_party/blink/renderer/core/style/computed_style.h" -#include "third_party/blink/renderer/platform/geometry/layout_rect.h" -#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" - -namespace blink { - -bool NGPhysicalOffsetRect::operator==(const NGPhysicalOffsetRect& other) const { - return other.offset == offset && other.size == size; -} - -bool NGPhysicalOffsetRect::Contains(const NGPhysicalOffsetRect& other) const { - return offset.left <= other.offset.left && offset.top <= other.offset.top && - Right() >= other.Right() && Bottom() >= other.Bottom(); -} - -NGPhysicalOffsetRect NGPhysicalOffsetRect::operator+( - const NGPhysicalOffset& offset) const { - return {this->offset + offset, size}; -} - -void NGPhysicalOffsetRect::Unite(const NGPhysicalOffsetRect& other) { - if (other.IsEmpty()) - return; - if (IsEmpty()) { - *this = other; - return; - } - - UniteEvenIfEmpty(other); -} - -void NGPhysicalOffsetRect::UniteIfNonZero(const NGPhysicalOffsetRect& other) { - if (other.size.IsZero()) - return; - if (size.IsZero()) { - *this = other; - return; - } - - UniteEvenIfEmpty(other); -} - -void NGPhysicalOffsetRect::UniteEvenIfEmpty(const NGPhysicalOffsetRect& other) { - LayoutUnit left = std::min(offset.left, other.offset.left); - LayoutUnit top = std::min(offset.top, other.offset.top); - LayoutUnit right = std::max(Right(), other.Right()); - LayoutUnit bottom = std::max(Bottom(), other.Bottom()); - offset = {left, top}; - size = {right - left, bottom - top}; -} - -void NGPhysicalOffsetRect::Expand(const NGPhysicalBoxStrut& strut) { - if (strut.top) { - offset.top -= strut.top; - size.height += strut.top; - } - if (strut.bottom) { - size.height += strut.bottom; - } - if (strut.left) { - offset.left -= strut.left; - size.width += strut.left; - } - if (strut.right) { - size.width += strut.right; - } -} - -void NGPhysicalOffsetRect::ExpandEdgesToPixelBoundaries() { - int left = FloorToInt(offset.left); - int top = FloorToInt(offset.top); - int max_right = (offset.left + size.width).Ceil(); - int max_bottom = (offset.top + size.height).Ceil(); - offset.left = LayoutUnit(left); - offset.top = LayoutUnit(top); - size.width = LayoutUnit(max_right - left); - size.height = LayoutUnit(max_bottom - top); -} - -NGPhysicalOffsetRect::NGPhysicalOffsetRect(const LayoutRect& source) - : NGPhysicalOffsetRect({source.X(), source.Y()}, - {source.Width(), source.Height()}) {} - -LayoutRect NGPhysicalOffsetRect::ToLayoutRect() const { - return {offset.left, offset.top, size.width, size.height}; -} - -LayoutRect NGPhysicalOffsetRect::ToLayoutFlippedRect( - const ComputedStyle& style, - const NGPhysicalSize& container_size) const { - if (!style.IsFlippedBlocksWritingMode()) - return {offset.left, offset.top, size.width, size.height}; - return {container_size.width - offset.left - size.width, offset.top, - size.width, size.height}; -} - -FloatRect NGPhysicalOffsetRect::ToFloatRect() const { - return {offset.left, offset.top, size.width, size.height}; -} - -String NGPhysicalOffsetRect::ToString() const { - return String::Format("%s,%s %sx%s", offset.left.ToString().Ascii().data(), - offset.top.ToString().Ascii().data(), - size.width.ToString().Ascii().data(), - size.height.ToString().Ascii().data()); -} - -std::ostream& operator<<(std::ostream& os, const NGPhysicalOffsetRect& value) { - return os << value.ToString(); -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h deleted file mode 100644 index 2ef735b6c7c..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h +++ /dev/null @@ -1,65 +0,0 @@ -// 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. - -#ifndef NGPhysicalOffsetRect_h -#define NGPhysicalOffsetRect_h - -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" -#include "third_party/blink/renderer/platform/geometry/layout_unit.h" - -namespace blink { - -class ComputedStyle; -class FloatRect; -class LayoutRect; -struct NGPhysicalBoxStrut; - -// NGPhysicalOffsetRect is the position and size of a rect (typically a -// fragment) relative to its parent rect in the physical coordinate system. -struct CORE_EXPORT NGPhysicalOffsetRect { - NGPhysicalOffsetRect() = default; - NGPhysicalOffsetRect(const NGPhysicalOffset& offset, - const NGPhysicalSize& size) - : offset(offset), size(size) {} - - NGPhysicalOffset offset; - NGPhysicalSize size; - - bool IsEmpty() const { return size.IsEmpty(); } - LayoutUnit Right() const { return offset.left + size.width; } - LayoutUnit Bottom() const { return offset.top + size.height; } - - bool operator==(const NGPhysicalOffsetRect& other) const; - - bool Contains(const NGPhysicalOffsetRect& other) const; - - NGPhysicalOffsetRect operator+(const NGPhysicalOffset&) const; - - void Unite(const NGPhysicalOffsetRect&); - void UniteIfNonZero(const NGPhysicalOffsetRect&); - void UniteEvenIfEmpty(const NGPhysicalOffsetRect&); - - void Expand(const NGPhysicalBoxStrut&); - void ExpandEdgesToPixelBoundaries(); - - // Conversions from/to existing code. New code prefers type safety for - // logical/physical distinctions. - explicit NGPhysicalOffsetRect(const LayoutRect&); - LayoutRect ToLayoutRect() const; - LayoutRect ToLayoutFlippedRect(const ComputedStyle&, - const NGPhysicalSize&) const; - - FloatRect ToFloatRect() const; - - String ToString() const; -}; - -CORE_EXPORT std::ostream& operator<<(std::ostream&, - const NGPhysicalOffsetRect&); - -} // namespace blink - -#endif // NGPhysicalOffsetRect_h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect_test.cc deleted file mode 100644 index ac7bc56b54b..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect_test.cc +++ /dev/null @@ -1,61 +0,0 @@ -// 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/geometry/ng_physical_offset_rect.h" - -#include "testing/gtest/include/gtest/gtest.h" - -namespace blink { - -namespace { - -struct PhysicalOffsetRectUniteTestData { - const char* test_case; - NGPhysicalOffsetRect a; - NGPhysicalOffsetRect b; - NGPhysicalOffsetRect expected; -} physical_offset_rect_unite_test_data[] = { - {"all_empty", {}, {}, {}}, - {"a empty", - {}, - {{LayoutUnit(1), LayoutUnit(2)}, {LayoutUnit(3), LayoutUnit(4)}}, - {{LayoutUnit(1), LayoutUnit(2)}, {LayoutUnit(3), LayoutUnit(4)}}}, - {"b empty", - {{LayoutUnit(1), LayoutUnit(2)}, {LayoutUnit(3), LayoutUnit(4)}}, - {}, - {{LayoutUnit(1), LayoutUnit(2)}, {LayoutUnit(3), LayoutUnit(4)}}}, - {"a larger", - {{LayoutUnit(100), LayoutUnit(50)}, {LayoutUnit(300), LayoutUnit(200)}}, - {{LayoutUnit(200), LayoutUnit(50)}, {LayoutUnit(200), LayoutUnit(200)}}, - {{LayoutUnit(100), LayoutUnit(50)}, {LayoutUnit(300), LayoutUnit(200)}}}, - {"b larger", - {{LayoutUnit(200), LayoutUnit(50)}, {LayoutUnit(200), LayoutUnit(200)}}, - {{LayoutUnit(100), LayoutUnit(50)}, {LayoutUnit(300), LayoutUnit(200)}}, - {{LayoutUnit(100), LayoutUnit(50)}, {LayoutUnit(300), LayoutUnit(200)}}}, -}; - -std::ostream& operator<<(std::ostream& os, - const PhysicalOffsetRectUniteTestData& data) { - return os << "Unite " << data.test_case; -} - -class NGPhysicalOffsetRectUniteTest - : public testing::Test, - public testing::WithParamInterface<PhysicalOffsetRectUniteTestData> {}; - -INSTANTIATE_TEST_SUITE_P( - NGGeometryUnitsTest, - NGPhysicalOffsetRectUniteTest, - testing::ValuesIn(physical_offset_rect_unite_test_data)); - -TEST_P(NGPhysicalOffsetRectUniteTest, Data) { - const auto& data = GetParam(); - NGPhysicalOffsetRect actual = data.a; - actual.Unite(data.b); - EXPECT_EQ(data.expected, actual); -} - -} // namespace - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_test.cc deleted file mode 100644 index 4c85a2dbe0a..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_test.cc +++ /dev/null @@ -1,74 +0,0 @@ -// 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/geometry/ng_physical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" - -#include "testing/gtest/include/gtest/gtest.h" - -namespace blink { - -namespace { - -TEST(NGGeometryUnitsTest, ConvertPhysicalOffsetToLogicalOffset) { - NGPhysicalOffset physical_offset(LayoutUnit(20), LayoutUnit(30)); - NGPhysicalSize outer_size(LayoutUnit(300), LayoutUnit(400)); - NGPhysicalSize inner_size(LayoutUnit(5), LayoutUnit(65)); - NGLogicalOffset offset; - - offset = physical_offset.ConvertToLogical( - WritingMode::kHorizontalTb, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(20), offset.inline_offset); - EXPECT_EQ(LayoutUnit(30), offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kHorizontalTb, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(275), offset.inline_offset); - EXPECT_EQ(LayoutUnit(30), offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kVerticalRl, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(30), offset.inline_offset); - EXPECT_EQ(LayoutUnit(275), offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kVerticalRl, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(305), offset.inline_offset); - EXPECT_EQ(LayoutUnit(275), offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kSidewaysRl, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(30), offset.inline_offset); - EXPECT_EQ(LayoutUnit(275), offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kSidewaysRl, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(305), offset.inline_offset); - EXPECT_EQ(LayoutUnit(275), offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kVerticalLr, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(30), offset.inline_offset); - EXPECT_EQ(LayoutUnit(20), offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kVerticalLr, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(305), offset.inline_offset); - EXPECT_EQ(LayoutUnit(20), offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kSidewaysLr, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(305), offset.inline_offset); - EXPECT_EQ(LayoutUnit(20), offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kSidewaysLr, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(LayoutUnit(30), offset.inline_offset); - EXPECT_EQ(LayoutUnit(20), offset.block_offset); -} - -} // namespace - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.cc deleted file mode 100644 index e8661406658..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.cc +++ /dev/null @@ -1,19 +0,0 @@ -// 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. - -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" - -#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" - -namespace blink { - -String NGPhysicalSize::ToString() const { - return String::Format("%dx%d", width.ToInt(), height.ToInt()); -} - -std::ostream& operator<<(std::ostream& os, const NGPhysicalSize& value) { - return os << value.ToString(); -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h deleted file mode 100644 index cbeb617810c..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h +++ /dev/null @@ -1,66 +0,0 @@ -// 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 NGPhysicalSize_h -#define NGPhysicalSize_h - -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" -#include "third_party/blink/renderer/platform/geometry/layout_size.h" -#include "third_party/blink/renderer/platform/geometry/layout_unit.h" -#include "third_party/blink/renderer/platform/text/writing_mode.h" - -namespace blink { - -class LayoutSize; -struct NGLogicalSize; - -// NGPhysicalSize is the size of a rect (typically a fragment) in the physical -// coordinate system. -struct CORE_EXPORT NGPhysicalSize { - NGPhysicalSize() = default; - NGPhysicalSize(LayoutUnit width, LayoutUnit height) - : width(width), height(height) {} - - LayoutUnit width; - LayoutUnit height; - - NGLogicalSize ConvertToLogical(WritingMode mode) const { - return mode == WritingMode::kHorizontalTb ? NGLogicalSize(width, height) - : NGLogicalSize(height, width); - } - - bool operator==(const NGPhysicalSize& other) const { - return std::tie(other.width, other.height) == std::tie(width, height); - } - bool operator!=(const NGPhysicalSize& other) const { - return !(*this == other); - } - - bool IsEmpty() const { - return width == LayoutUnit() || height == LayoutUnit(); - } - bool IsZero() const { - return width == LayoutUnit() && height == LayoutUnit(); - } - - // Conversions from/to existing code. New code prefers type safety for - // logical/physical distinctions. - LayoutSize ToLayoutSize() const { return {width, height}; } - - String ToString() const; -}; - -CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGPhysicalSize&); - -inline NGPhysicalSize ToNGPhysicalSize(const NGLogicalSize& other, - WritingMode mode) { - return mode == WritingMode::kHorizontalTb - ? NGPhysicalSize(other.inline_size, other.block_size) - : NGPhysicalSize(other.block_size, other.inline_size); -} - -} // namespace blink - -#endif // NGPhysicalSize_h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_static_position.cc b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_static_position.cc index f80b46bac4a..b90c8e35d33 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_static_position.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_static_position.cc @@ -8,7 +8,7 @@ namespace blink { NGStaticPosition NGStaticPosition::Create(WritingMode writing_mode, TextDirection direction, - NGPhysicalOffset offset) { + PhysicalOffset offset) { NGStaticPosition position; position.offset = offset; switch (writing_mode) { @@ -72,24 +72,4 @@ LayoutUnit NGStaticPosition::BottomInset(LayoutUnit container_size, return container_size - offset.top; } -LayoutUnit NGStaticPosition::Left() const { - DCHECK(HasLeft()); - return offset.left; -} - -LayoutUnit NGStaticPosition::Right() const { - DCHECK(!HasLeft()); - return offset.left; -} - -LayoutUnit NGStaticPosition::Top() const { - DCHECK(HasTop()); - return offset.top; -} - -LayoutUnit NGStaticPosition::Bottom() const { - DCHECK(!HasTop()); - return offset.top; -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_static_position.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_static_position.h index 7bd2d6cfd2b..68f72b4cd61 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_static_position.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_static_position.h @@ -6,7 +6,7 @@ #define NGStaticPosition_h #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_offset.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h" #include "third_party/blink/renderer/platform/text/text_direction.h" #include "third_party/blink/renderer/platform/text/writing_mode.h" @@ -18,11 +18,11 @@ struct CORE_EXPORT NGStaticPosition { enum Type { kTopLeft, kTopRight, kBottomLeft, kBottomRight }; Type type; // Logical corner that corresponds to physical top left. - NGPhysicalOffset offset; + PhysicalOffset offset; // Creates a position with proper type wrt writing mode and direction. // It expects physical offset of inline_start/block_start vertex. - static NGStaticPosition Create(WritingMode, TextDirection, NGPhysicalOffset); + static NGStaticPosition Create(WritingMode, TextDirection, PhysicalOffset); // Left/Right/TopPosition functions map static position to inset of // left/right/top edge wrt container space. @@ -45,10 +45,25 @@ struct CORE_EXPORT NGStaticPosition { LayoutUnit margin_top, LayoutUnit margin_bottom) const; - LayoutUnit Left() const; - LayoutUnit Right() const; - LayoutUnit Top() const; - LayoutUnit Bottom() const; + LayoutUnit Left() const { + DCHECK(HasLeft()); + return offset.left; + } + + LayoutUnit Right() const { + DCHECK(!HasLeft()); + return offset.left; + } + + LayoutUnit Top() const { + DCHECK(HasTop()); + return offset.top; + } + + LayoutUnit Bottom() const { + DCHECK(!HasTop()); + return offset.top; + } bool HasTop() const { return type == kTopLeft || type == kTopRight; } bool HasLeft() const { return type == kTopLeft || type == kBottomLeft; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md b/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md index c604426d29e..a9b42d7c716 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md @@ -309,11 +309,6 @@ In a bird's‐eye view, it consists of two parts: content of an inline formatting context (computed in [pre-layout]) and DOM positions in the context. See [design doc](https://goo.gl/CJbxky) for details. -[NGCaretNavigator] provides functions for inspecting bidi levels and visual -ordering of text content, and supports visual left/right caret movements in the -text. See [design doc](http://bit.ly/2QVAwGq) for details. - - [ICU BiDi]: http://userguide.icu-project.org/transforms/bidi [UAX#9 Unicode Bidirectional Algorithm]: http://unicode.org/reports/tr9/ [UAX#9 Resolving Embedding Levels]: http://www.unicode.org/reports/tr9/#Resolving_Embedding_Levels @@ -322,7 +317,6 @@ text. See [design doc](http://bit.ly/2QVAwGq) for details. [FontBaseline]: ../../../platform/fonts/FontBaseline.h [NGBaselineAlgorithmType]: ng_baseline.h [NGBaselineRequest]: ng_baseline.h -[NGCaretNavigator]: ng_caret_navigator.h [NGBidiParagraph]: ng_bidi_paragraph.h [NGBlockNode]: ../ng_block_node.h [NGBoxFragment]: ../ng_box_fragment.h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h index d922dbf5684..4e2517fba32 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h @@ -23,12 +23,6 @@ class CORE_EXPORT LayoutNGText : public LayoutText { } bool IsLayoutNGObject() const override { return true; } - protected: - void InsertedIntoTree() override { - valid_ng_items_ = false; - LayoutText::InsertedIntoTree(); - } - private: const NGInlineItems* GetNGInlineItems() const final { return &inline_items_; } NGInlineItems* GetNGInlineItems() final { return &inline_items_; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc index 0a93d49372b..7fe1900ab55 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h" #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" @@ -26,12 +27,16 @@ scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::GetOrCreate( new FragmentToNGAbstractInlineTextBoxHashMap(); } const auto it = g_abstract_inline_text_box_map_->find(&fragment); - if (it != g_abstract_inline_text_box_map_->end()) + LayoutText* const layout_text = + ToLayoutText(fragment.GetMutableLayoutObject()); + if (it != g_abstract_inline_text_box_map_->end()) { + CHECK(layout_text->HasAbstractInlineTextBox()); return it->value; - scoped_refptr<AbstractInlineTextBox> obj = - base::AdoptRef(new NGAbstractInlineTextBox( - LineLayoutText(ToLayoutText(fragment.GetLayoutObject())), fragment)); + } + scoped_refptr<AbstractInlineTextBox> obj = base::AdoptRef( + new NGAbstractInlineTextBox(LineLayoutText(layout_text), fragment)); g_abstract_inline_text_box_map_->Set(&fragment, obj); + layout_text->SetHasAbstractInlineTextBox(); return obj; } @@ -65,12 +70,6 @@ void NGAbstractInlineTextBox::Detach() { fragment_ = nullptr; } -bool NGAbstractInlineTextBox::HasSoftWrapToNextLine() const { - return To<NGPhysicalLineBoxFragment>( - fragment_->ContainerLineBox()->PhysicalFragment()) - .HasSoftWrapToNextLine(); -} - const NGPhysicalTextFragment& NGAbstractInlineTextBox::PhysicalTextFragment() const { return To<NGPhysicalTextFragment>(fragment_->PhysicalFragment()); @@ -81,13 +80,34 @@ bool NGAbstractInlineTextBox::NeedsLayout() const { } bool NGAbstractInlineTextBox::NeedsTrailingSpace() const { - if (!HasSoftWrapToNextLine()) + if (!fragment_->Style().CollapseWhiteSpace()) return false; - const NGPaintFragment* next_fragment = NextTextFragmentForSameLayoutObject(); - if (!next_fragment) + const NGPaintFragment& line_box = *fragment_->ContainerLineBox(); + if (!To<NGPhysicalLineBoxFragment>(line_box.PhysicalFragment()) + .HasSoftWrapToNextLine()) + return false; + const NGPhysicalTextFragment& text_fragment = PhysicalTextFragment(); + if (text_fragment.EndOffset() >= text_fragment.TextContent().length()) + return false; + if (text_fragment.TextContent()[text_fragment.EndOffset()] != ' ') + return false; + const NGInlineBreakToken& break_token = *To<NGInlineBreakToken>( + To<NGPhysicalLineBoxFragment>(line_box.PhysicalFragment()).BreakToken()); + // TODO(yosin): We should support OOF fragments between |fragment_| and + // break token. + if (break_token.TextOffset() != text_fragment.EndOffset() + 1) + return false; + // Check a character in text content after |fragment_| comes from same + // layout text of |fragment_|. + const NGOffsetMapping& mapping = + *NGOffsetMapping::GetFor(fragment_->GetLayoutObject()); + const NGMappingUnitRange& mapping_units = + mapping.GetMappingUnitsForTextContentOffsetRange( + text_fragment.EndOffset(), text_fragment.EndOffset() + 1); + if (mapping_units.begin() == mapping_units.end()) return false; - return To<NGPhysicalTextFragment>(next_fragment->PhysicalFragment()) - .StartOffset() != PhysicalTextFragment().EndOffset(); + const NGOffsetMappingUnit* const mapping_unit = mapping_units.begin(); + return mapping_unit->GetLayoutObject() == fragment_->GetLayoutObject(); } const NGPaintFragment* diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h index d74e962fc41..fb2368ed230 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h @@ -32,7 +32,6 @@ class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox { NGAbstractInlineTextBox(LineLayoutText line_layout_item, const NGPaintFragment& fragment); - bool HasSoftWrapToNextLine() const; const NGPhysicalTextFragment& PhysicalTextFragment() const; bool NeedsLayout() const; bool NeedsTrailingSpace() const; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h index 22fc93bf751..be83e72efc7 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h @@ -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) diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc deleted file mode 100644 index 5b2f37f4e13..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc +++ /dev/null @@ -1,426 +0,0 @@ -// Copyright 2018 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/inline/ng_caret_navigator.h" - -#include "third_party/blink/renderer/core/layout/layout_text_fragment.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.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_physical_box_fragment.h" - -namespace blink { - -std::ostream& operator<<(std::ostream& out, - const NGCaretNavigator::Position& position) { - return out << position.index << "/" - << (position.IsBeforeCharacter() ? "BeforeCharacter" - : "AfterCharacter"); -} - -NGCaretNavigator::~NGCaretNavigator() = default; - -NGCaretNavigator::NGCaretNavigator(const LayoutBlockFlow& context) - : context_(context), - disallow_transition_(context.GetDocument().Lifecycle()) { - DCHECK(RuntimeEnabledFeatures::BidiCaretAffinityEnabled()); - DCHECK(context.IsLayoutNGMixin()); - DCHECK(context.ChildrenInline()); - DCHECK(context.GetNGInlineNodeData()); - DCHECK(!context.GetDocument().NeedsLayoutTreeUpdate()); -} - -const NGInlineNodeData& NGCaretNavigator::GetData() const { - return *context_.GetNGInlineNodeData(); -} - -const String& NGCaretNavigator::GetText() const { - return GetData().text_content; -} - -bool NGCaretNavigator::IsBidiEnabled() const { - return GetData().IsBidiEnabled(); -} - -UBiDiLevel NGCaretNavigator::BidiLevelAt(unsigned index) const { - DCHECK_LT(index, GetText().length()); - if (!IsBidiEnabled()) - return 0; - return GetData().FindItemForTextOffset(index).BidiLevel(); -} - -TextDirection NGCaretNavigator::TextDirectionAt(unsigned index) const { - UBiDiLevel level = BidiLevelAt(index); - return DirectionFromLevel(level); -} - -bool NGCaretNavigator::OffsetIsBidiBoundary(unsigned offset) const { - DCHECK_LE(offset, GetText().length()); - if (!IsBidiEnabled()) - return false; - if (!offset || offset == GetText().length()) - return false; - return BidiLevelAt(offset - 1) != BidiLevelAt(offset); -} - -NGCaretNavigator::Position -NGCaretNavigator::CaretPositionFromTextContentOffsetAndAffinity( - unsigned offset, - TextAffinity affinity) const { - DCHECK_LE(offset, GetText().length()); - // Callers sometimes pass in (0, upstream) or (length, downstream), which - // originate from legacy callers. Make sure they are fixed up. - // TODO(xiaochengh): Catch and eliminate such callers. - if (affinity == TextAffinity::kUpstream) { - if (offset) - return {offset - 1, PositionAnchorType::kAfter}; - return {0, PositionAnchorType::kBefore}; - } - - if (offset < GetText().length()) - return {offset, PositionAnchorType::kBefore}; - return {GetText().length() - 1, PositionAnchorType::kAfter}; -} - -// static -NGCaretNavigator::MoveDirection NGCaretNavigator::OppositeDirectionOf( - MoveDirection direction) { - if (direction == MoveDirection::kTowardsLeft) - return MoveDirection::kTowardsRight; - return MoveDirection::kTowardsLeft; -} - -// static -bool NGCaretNavigator::TowardsSameDirection(MoveDirection move_direction, - TextDirection text_direction) { - if (IsLtr(text_direction)) - return move_direction == MoveDirection::kTowardsRight; - return move_direction == MoveDirection::kTowardsLeft; -} - -NGCaretNavigator::Line NGCaretNavigator::ContainingLineOf( - unsigned index) const { - DCHECK_LT(index, GetText().length()); - // TODO(xiaochengh): Make it work for multi-col - DCHECK(context_.CurrentFragment()); - unsigned last_line_end = 0; - for (const auto child : context_.CurrentFragment()->Children()) { - if (!child->IsLineBox()) - continue; - const auto* line = To<NGPhysicalLineBoxFragment>(child.get()); - const auto* token = To<NGInlineBreakToken>(line->BreakToken()); - const unsigned line_end = - token->IsFinished() ? GetText().length() : token->TextOffset(); - if (line_end > index) - return {last_line_end, line_end, line->BaseDirection()}; - last_line_end = line_end; - } - NOTREACHED(); - return {}; -} - -bool NGCaretNavigator::IsValidCaretPosition(const Position& position) const { - unsigned index = position.index; - if (position.IsAfterCharacter() && IsLineBreak(index)) - return false; - if (IsCollapsedSpaceByLineWrap(index)) - return false; - if (IsIgnoredInCaretMovement(index)) - return false; - return true; -} - -bool NGCaretNavigator::IsCollapsibleWhitespace(unsigned index) const { - DCHECK_LT(index, GetText().length()); - if (GetText()[index] != kSpaceCharacter) - return false; - const NGInlineItem& item = GetData().FindItemForTextOffset(index); - return item.Style()->CollapseWhiteSpace(); -} - -bool NGCaretNavigator::IsLineBreak(unsigned index) const { - DCHECK_LT(index, GetText().length()); - return GetText()[index] == kNewlineCharacter; -} - -bool NGCaretNavigator::IsCollapsedSpaceByLineWrap(unsigned index) const { - DCHECK_LT(index, GetText().length()); - if (!IsCollapsibleWhitespace(index)) - return false; - return index + 1 == ContainingLineOf(index).end_offset; -} - -bool NGCaretNavigator::IsIgnoredInCaretMovement(unsigned index) const { - DCHECK_LT(index, GetText().length()); - const NGInlineItem& item = GetData().FindItemForTextOffset(index); - - // Caret navigation works on text, atomic inlines and non-ZWS controls only. - switch (item.Type()) { - case NGInlineItem::kText: - case NGInlineItem::kAtomicInline: - break; - case NGInlineItem::kControl: - if (GetText()[index] == kZeroWidthSpaceCharacter) - return true; - break; - default: - return true; - } - - // Ignore CSS generate contents. - // TODO(xiaochengh): This might be general enough to be merged into - // |NGInlineItem| as a member function. - DCHECK(item.GetLayoutObject()); - const LayoutObject* object = item.GetLayoutObject(); - if (const auto* text_fragment = ToLayoutTextFragmentOrNull(object)) { - // ::first-letter |LayoutTextFragment| returns null for |GetNode()|. Check - // |AssociatedTextNode()| to see if it's created by a text node. - if (!text_fragment->AssociatedTextNode()) - return true; - } else { - if (!object->NonPseudoNode()) - return true; - } - - // Ignore collapsed whitespaces that not visually at line end due to bidi. - // Caret movement should move over them as if they don't exist to match the - // existing behavior. - return IsCollapsedSpaceByLineWrap(index) && - index != VisualLastCharacterOf(ContainingLineOf(index)); -} - -bool NGCaretNavigator::IsEnterableChildContext(unsigned index) const { - DCHECK_LT(index, GetText().length()); - if (GetText()[index] != kObjectReplacementCharacter) - return false; - - const NGInlineItem& item = GetData().FindItemForTextOffset(index); - if (item.Type() != NGInlineItem::kAtomicInline) - return false; - DCHECK(item.GetLayoutObject()); - const LayoutObject* object = item.GetLayoutObject(); - if (!object->IsLayoutBlockFlow()) - return false; - if (!object->NonPseudoNode() || !object->GetNode()->IsElementNode()) - return false; - const Element* node = ToElement(object->GetNode()); - return !node->GetShadowRoot() || !node->GetShadowRoot()->IsUserAgent(); -} - -NGCaretNavigator::Position NGCaretNavigator::LeftEdgeOf(unsigned index) const { - return EdgeOfInternal(index, MoveDirection::kTowardsLeft); -} - -NGCaretNavigator::Position NGCaretNavigator::RightEdgeOf(unsigned index) const { - return EdgeOfInternal(index, MoveDirection::kTowardsRight); -} - -NGCaretNavigator::Position NGCaretNavigator::EdgeOfInternal( - unsigned index, - MoveDirection edge_direction) const { - DCHECK_LT(index, GetText().length()); - const TextDirection character_direction = TextDirectionAt(index); - return {index, TowardsSameDirection(edge_direction, character_direction) - ? PositionAnchorType::kAfter - : PositionAnchorType::kBefore}; -} - -NGCaretNavigator::VisualCharacterMovementResult -NGCaretNavigator::LeftCharacterOf(unsigned index) const { - return MoveCharacterInternal(index, MoveDirection::kTowardsLeft); -} - -NGCaretNavigator::VisualCharacterMovementResult -NGCaretNavigator::RightCharacterOf(unsigned index) const { - return MoveCharacterInternal(index, MoveDirection::kTowardsRight); -} - -Vector<int32_t, 32> NGCaretNavigator::CharacterIndicesInVisualOrder( - const Line& line) const { - DCHECK(IsBidiEnabled()); - - Vector<UBiDiLevel, 32> levels; - levels.ReserveCapacity(line.end_offset - line.start_offset); - for (unsigned i = line.start_offset; i < line.end_offset; ++i) - levels.push_back(BidiLevelAt(i)); - - Vector<int32_t, 32> indices(levels.size()); - NGBidiParagraph::IndicesInVisualOrder(levels, &indices); - - for (auto& index : indices) - index += line.start_offset; - return indices; -} - -unsigned NGCaretNavigator::VisualMostForwardCharacterOf( - const Line& line, - MoveDirection direction) const { - if (!IsBidiEnabled()) { - if (direction == MoveDirection::kTowardsLeft) - return line.start_offset; - return line.end_offset - 1; - } - - const auto indices_in_visual_order = CharacterIndicesInVisualOrder(line); - if (direction == MoveDirection::kTowardsLeft) - return indices_in_visual_order.front(); - return indices_in_visual_order.back(); -} - -unsigned NGCaretNavigator::VisualFirstCharacterOf(const Line& line) const { - return VisualMostForwardCharacterOf(line, IsLtr(line.base_direction) - ? MoveDirection::kTowardsLeft - : MoveDirection::kTowardsRight); -} - -unsigned NGCaretNavigator::VisualLastCharacterOf(const Line& line) const { - return VisualMostForwardCharacterOf(line, IsLtr(line.base_direction) - ? MoveDirection::kTowardsRight - : MoveDirection::kTowardsLeft); -} - -NGCaretNavigator::VisualCharacterMovementResult -NGCaretNavigator::MoveCharacterInternal(unsigned index, - MoveDirection move_direction) const { - const Line line = ContainingLineOf(index); - - if (index == VisualMostForwardCharacterOf(line, move_direction)) { - if (TowardsSameDirection(move_direction, line.base_direction)) { - if (line.end_offset == GetText().length()) - return {VisualMovementResultType::kAfterContext, base::nullopt}; - const Line next_line = ContainingLineOf(line.end_offset); - return {VisualMovementResultType::kWithinContext, - VisualFirstCharacterOf(next_line)}; - } - - if (!line.start_offset) - return {VisualMovementResultType::kBeforeContext, base::nullopt}; - const Line last_line = ContainingLineOf(line.start_offset - 1); - return {VisualMovementResultType::kWithinContext, - VisualLastCharacterOf(last_line)}; - } - - if (!IsBidiEnabled()) { - if (move_direction == MoveDirection::kTowardsLeft) - return {VisualMovementResultType::kWithinContext, index - 1}; - return {VisualMovementResultType::kWithinContext, index + 1}; - } - - Vector<int32_t, 32> indices_in_visual_order = - CharacterIndicesInVisualOrder(line); - const int32_t* visual_location = std::find( - indices_in_visual_order.begin(), indices_in_visual_order.end(), index); - DCHECK_NE(visual_location, indices_in_visual_order.end()); - if (move_direction == MoveDirection::kTowardsLeft) { - DCHECK_NE(visual_location, indices_in_visual_order.begin()); - return {VisualMovementResultType::kWithinContext, - *std::prev(visual_location)}; - } - DCHECK_NE(std::next(visual_location), indices_in_visual_order.end()); - return {VisualMovementResultType::kWithinContext, - *std::next(visual_location)}; -} - -NGCaretNavigator::VisualCaretMovementResult NGCaretNavigator::LeftPositionOf( - const Position& caret_position) const { - return MoveCaretInternal(caret_position, MoveDirection::kTowardsLeft); -} - -NGCaretNavigator::VisualCaretMovementResult NGCaretNavigator::RightPositionOf( - const Position& caret_position) const { - return MoveCaretInternal(caret_position, MoveDirection::kTowardsRight); -} - -NGCaretNavigator::UnvalidatedVisualCaretMovementResult -NGCaretNavigator::MoveCaretWithoutValidation( - const Position& caret_position, - MoveDirection move_direction) const { - const unsigned index = caret_position.index; - const MoveDirection opposite_direction = OppositeDirectionOf(move_direction); - if (caret_position == EdgeOfInternal(index, opposite_direction)) { - // TODO(xiaochengh): Consider grapheme cluster - return {VisualMovementResultType::kWithinContext, - EdgeOfInternal(index, move_direction), - !IsIgnoredInCaretMovement(index)}; - } - - VisualCharacterMovementResult forward_character = - MoveCharacterInternal(index, move_direction); - if (!forward_character.IsWithinContext()) - return {forward_character.type}; - - DCHECK(forward_character.index.has_value()); - const Position forward_caret = - EdgeOfInternal(*forward_character.index, opposite_direction); - return {VisualMovementResultType::kWithinContext, forward_caret}; -} - -NGCaretNavigator::VisualCaretMovementResult NGCaretNavigator::MoveCaretInternal( - const Position& caret_position, - MoveDirection move_direction) const { - bool has_passed_character = false; - base::Optional<Position> last_position; - for (Position runner = caret_position; - !has_passed_character || !IsValidCaretPosition(runner);) { - const UnvalidatedVisualCaretMovementResult next = - MoveCaretWithoutValidation(runner, move_direction); - if (next.type != VisualMovementResultType::kWithinContext) - return {next.type, base::nullopt}; - - if (next.has_passed_character) { - has_passed_character = true; - - const unsigned last_passed_character = next.position->index; - if (IsEnterableChildContext(last_passed_character)) - return {VisualMovementResultType::kEnteredChildContext, runner}; - } - - runner = *next.position; - last_position = runner; - - // TODO(xiaochengh): Handle the case where we reach a different line with a - // different base direction, which occurs with 'unicode-bidi: plain-text'. - } - DCHECK(last_position.has_value()); - return {VisualMovementResultType::kWithinContext, *last_position}; -} - -NGCaretNavigator::Position NGCaretNavigator::LeftmostPositionInFirstLine() - const { - Line first_line = ContainingLineOf(0); - unsigned leftmost_character = - VisualMostForwardCharacterOf(first_line, MoveDirection::kTowardsLeft); - // TODO(xiaochengh): Handle if the caret position is invalid. - return LeftEdgeOf(leftmost_character); -} - -NGCaretNavigator::Position NGCaretNavigator::RightmostPositionInFirstLine() - const { - Line first_line = ContainingLineOf(0); - unsigned rightmost_character = - VisualMostForwardCharacterOf(first_line, MoveDirection::kTowardsRight); - // TODO(xiaochengh): Handle if the caret position is invalid. - return RightEdgeOf(rightmost_character); -} - -NGCaretNavigator::Position NGCaretNavigator::LeftmostPositionInLastLine() - const { - Line last_line = ContainingLineOf(GetText().length() - 1); - unsigned leftmost_character = - VisualMostForwardCharacterOf(last_line, MoveDirection::kTowardsLeft); - // TODO(xiaochengh): Handle if the caret position is invalid. - return LeftEdgeOf(leftmost_character); -} - -NGCaretNavigator::Position NGCaretNavigator::RightmostPositionInLastLine() - const { - Line last_line = ContainingLineOf(GetText().length() - 1); - unsigned rightmost_character = - VisualMostForwardCharacterOf(last_line, MoveDirection::kTowardsRight); - // TODO(xiaochengh): Handle if the caret position is invalid. - return RightEdgeOf(rightmost_character); -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h deleted file mode 100644 index 6c60d7f9f90..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2018 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_INLINE_NG_CARET_NAVIGATOR_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_NAVIGATOR_H_ - -#include "base/optional.h" -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/dom/document_lifecycle.h" -#include "third_party/blink/renderer/core/editing/text_affinity.h" -#include "third_party/blink/renderer/platform/text/text_direction.h" -#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" -#include "third_party/blink/renderer/platform/wtf/vector.h" - -#include <unicode/ubidi.h> - -namespace blink { - -class LayoutBlockFlow; -struct NGInlineNodeData; - -// Hosts the |text_content| of an inline formatting context and provides -// bidi-related utilities, including checking bidi levels, computing visual -// left/right characters and visual left/right caret movements. -// Design doc: http://bit.ly/2QVAwGq -class CORE_EXPORT NGCaretNavigator { - STACK_ALLOCATED(); - - public: - explicit NGCaretNavigator(const LayoutBlockFlow&); - ~NGCaretNavigator(); - - const String& GetText() const; - bool IsBidiEnabled() const; - - // Abstraction of a caret position in |text_|. - enum class PositionAnchorType { kBefore, kAfter }; - struct Position { - // |index| is character index the |text_| string. - unsigned index; - PositionAnchorType type; - - bool IsBeforeCharacter() const { - return type == PositionAnchorType::kBefore; - } - - bool IsAfterCharacter() const { return type == PositionAnchorType::kAfter; } - - bool operator==(const Position& other) const { - return index == other.index && type == other.type; - } - }; - - // Returns the bidi level or resolved direction of the character at the given - // logical |index|. - UBiDiLevel BidiLevelAt(unsigned index) const; - TextDirection TextDirectionAt(unsigned index) const; - - // Returns true if the characters at indexes |offset - 1| and |offset| both - // exist and are at different bidi levels. - bool OffsetIsBidiBoundary(unsigned offset) const; - - // Converts an (offset, affinity) pair into a |Position| type of this class. - // Intiontionally long name to indicate the hackiness for handling legacy - // callers. - Position CaretPositionFromTextContentOffsetAndAffinity( - unsigned offset, - TextAffinity affinity) const; - - // Returns the visual left/right edge caret position of the character at the - // given logical |index|. - Position LeftEdgeOf(unsigned index) const; - Position RightEdgeOf(unsigned index) const; - - // Left/right visual movements - // TODO(xiaochengh): Handle the following - // - Grapheme clusters - - enum class VisualMovementResultType { - kWithinContext, - kBeforeContext, - kAfterContext, - kEnteredChildContext - }; - - // Given the character at the logical |index|, returns the logical index of - // the character at its left/right side. - struct VisualCharacterMovementResult { - bool IsWithinContext() const { - return type == VisualMovementResultType::kWithinContext; - } - bool IsBeforeContext() const { - return type == VisualMovementResultType::kBeforeContext; - } - bool IsAfterContext() const { - return type == VisualMovementResultType::kAfterContext; - } - - VisualMovementResultType type; - base::Optional<unsigned> index; - }; - VisualCharacterMovementResult LeftCharacterOf(unsigned index) const; - VisualCharacterMovementResult RightCharacterOf(unsigned index) const; - - // Given a caret position, moves it left/right by one grapheme cluster and - // returns the result. - // Note: If we end up entering an inline block, the result |Position| is - // either before or after the inline block, depending on from which side the - // inline block is entered. For example: - // RightPositionOf(abc|<inline-block>def</inline-block>ghi) - // -> {inline-block, PositionAnchorType::kBefore} - // LeftPositionOf(abc<inline-block>def</inline-block>|ghi) - // -> {inline-block, PositionAnchorType::kAfter} - struct VisualCaretMovementResult { - bool IsWithinContext() const { - return type == VisualMovementResultType::kWithinContext; - } - bool IsBeforeContext() const { - return type == VisualMovementResultType::kBeforeContext; - } - bool IsAfterContext() const { - return type == VisualMovementResultType::kAfterContext; - } - bool HasEnteredChildContext() const { - return type == VisualMovementResultType::kEnteredChildContext; - } - - VisualMovementResultType type; - base::Optional<Position> position; - }; - VisualCaretMovementResult LeftPositionOf(const Position&) const; - VisualCaretMovementResult RightPositionOf(const Position&) const; - - // TODO(xiaochengh): Specify and implement the behavior in edge cases, e.g., - // when the leftmost character of the first line is CSS-generated. - Position LeftmostPositionInFirstLine() const; - Position RightmostPositionInFirstLine() const; - Position LeftmostPositionInLastLine() const; - Position RightmostPositionInLastLine() const; - - private: - // A caret position is invalid if it is: - // - kAfter to a line break character. - // - Anchored to a collapsible space that's removed by line wrap. - // - Anchored to a character that's ignored in caret movement. - bool IsValidCaretPosition(const Position&) const; - bool IsLineBreak(unsigned index) const; - bool IsCollapsibleWhitespace(unsigned index) const; - bool IsCollapsedSpaceByLineWrap(unsigned index) const; - bool IsIgnoredInCaretMovement(unsigned index) const; - - // Returns true if the character at |index| represents a child block - // formatting context that can be entered by caret navigation. Such contexts - // must be atomic inlines (inline block, inline table, ...) and must not host - // user agent shadow tree (which excludes, e.g., <input> and image alt text). - bool IsEnterableChildContext(unsigned index) const; - - enum class MoveDirection { kTowardsLeft, kTowardsRight }; - static MoveDirection OppositeDirectionOf(MoveDirection); - static bool TowardsSameDirection(MoveDirection, TextDirection); - - // ------ Line-related functions ------ - - // A line contains a consecutive substring of |GetText()|. The lines should - // not overlap, and should together cover the entire |GetText()|. - struct Line { - unsigned start_offset; - unsigned end_offset; - TextDirection base_direction; - }; - Line ContainingLineOf(unsigned index) const; - Vector<int32_t, 32> CharacterIndicesInVisualOrder(const Line&) const; - unsigned VisualMostForwardCharacterOf(const Line&, - MoveDirection direction) const; - unsigned VisualLastCharacterOf(const Line&) const; - unsigned VisualFirstCharacterOf(const Line&) const; - - // ------ Implementation of public visual movement functions ------ - - Position EdgeOfInternal(unsigned index, MoveDirection) const; - VisualCharacterMovementResult MoveCharacterInternal(unsigned index, - MoveDirection) const; - VisualCaretMovementResult MoveCaretInternal(const Position&, - MoveDirection) const; - - // Performs a "minimal" caret movement to the left/right without validating - // the result. The result might be invalid due to, e.g., anchored to an - // unallowed character, being visually the same as the input, etc. It's a - // subroutine of |MoveCaretInternal|, who keeps calling it until both of the - // folliwng are satisfied: - // - We've reached a valid caret position. - // - During the process, the caret has moved passing a character on which - // |IsIgnoredInCaretMovement| is false (indicated by |has_passed_character|). - struct UnvalidatedVisualCaretMovementResult { - VisualMovementResultType type; - base::Optional<Position> position; - bool has_passed_character = false; - }; - UnvalidatedVisualCaretMovementResult MoveCaretWithoutValidation( - const Position&, - MoveDirection) const; - - const NGInlineNodeData& GetData() const; - - const LayoutBlockFlow& context_; - DocumentLifecycle::DisallowTransitionScope disallow_transition_; -}; - -CORE_EXPORT std::ostream& operator<<(std::ostream&, - const NGCaretNavigator::Position&); - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_NAVIGATOR_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator_test.cc deleted file mode 100644 index 70c99450dd9..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator_test.cc +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright 2018 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/inline/ng_caret_navigator.h" - -#include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h" -#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" -#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" - -namespace blink { - -class NGCaretNavigatorTest : public RenderingTest, - private ScopedBidiCaretAffinityForTest { - public: - NGCaretNavigatorTest() : ScopedBidiCaretAffinityForTest(true) {} - - void SetupHtml(const char* id, String html) { - SetBodyInnerHTML(html); - - block_flow_ = To<LayoutBlockFlow>(GetLayoutObjectByElementId(id)); - DCHECK(block_flow_); - DCHECK(block_flow_->IsLayoutNGMixin()); - DCHECK(block_flow_->ChildrenInline()); - } - - UBiDiLevel BidiLevelAt(unsigned index) const { - return NGCaretNavigator(*block_flow_).BidiLevelAt(index); - } - - NGCaretNavigator::VisualCharacterMovementResult LeftCharacterOf( - unsigned index) const { - return NGCaretNavigator(*block_flow_).LeftCharacterOf(index); - } - - NGCaretNavigator::VisualCharacterMovementResult RightCharacterOf( - unsigned index) const { - return NGCaretNavigator(*block_flow_).RightCharacterOf(index); - } - - NGCaretNavigator::Position CaretBefore(unsigned index) const { - return {index, NGCaretNavigator::PositionAnchorType::kBefore}; - } - - NGCaretNavigator::Position CaretAfter(unsigned index) const { - return {index, NGCaretNavigator::PositionAnchorType::kAfter}; - } - - NGCaretNavigator::VisualCaretMovementResult LeftPositionOf( - const NGCaretNavigator::Position& position) const { - return NGCaretNavigator(*block_flow_).LeftPositionOf(position); - } - - NGCaretNavigator::VisualCaretMovementResult RightPositionOf( - const NGCaretNavigator::Position& position) const { - return NGCaretNavigator(*block_flow_).RightPositionOf(position); - } - - protected: - const LayoutBlockFlow* block_flow_; -}; - -TEST_F(NGCaretNavigatorTest, BidiLevelAtBasic) { - SetupHtml("container", - "<div id=container>abcאבג123</div>"); - - EXPECT_EQ(0u, BidiLevelAt(0)); - EXPECT_EQ(0u, BidiLevelAt(1)); - EXPECT_EQ(0u, BidiLevelAt(2)); - EXPECT_EQ(1u, BidiLevelAt(3)); - EXPECT_EQ(1u, BidiLevelAt(4)); - EXPECT_EQ(1u, BidiLevelAt(5)); - EXPECT_EQ(2u, BidiLevelAt(6)); - EXPECT_EQ(2u, BidiLevelAt(7)); - EXPECT_EQ(2u, BidiLevelAt(8)); -} - -TEST_F(NGCaretNavigatorTest, LeftCharacterOfBasic) { - SetupHtml("container", - "<div id=container>abcאבג123</div>"); - - EXPECT_TRUE(LeftCharacterOf(0).IsBeforeContext()); - - EXPECT_TRUE(LeftCharacterOf(1).IsWithinContext()); - EXPECT_EQ(0u, *LeftCharacterOf(1).index); - - EXPECT_TRUE(LeftCharacterOf(2).IsWithinContext()); - EXPECT_EQ(1u, *LeftCharacterOf(2).index); - - EXPECT_TRUE(LeftCharacterOf(3).IsWithinContext()); - EXPECT_EQ(4u, *LeftCharacterOf(3).index); - - EXPECT_TRUE(LeftCharacterOf(4).IsWithinContext()); - EXPECT_EQ(5u, *LeftCharacterOf(4).index); - - EXPECT_TRUE(LeftCharacterOf(5).IsWithinContext()); - EXPECT_EQ(8u, *LeftCharacterOf(5).index); - - EXPECT_TRUE(LeftCharacterOf(6).IsWithinContext()); - EXPECT_EQ(2u, *LeftCharacterOf(6).index); - - EXPECT_TRUE(LeftCharacterOf(7).IsWithinContext()); - EXPECT_EQ(6u, *LeftCharacterOf(7).index); - - EXPECT_TRUE(LeftCharacterOf(8).IsWithinContext()); - EXPECT_EQ(7u, *LeftCharacterOf(8).index); -} - -TEST_F(NGCaretNavigatorTest, RightCharacterOfBasic) { - SetupHtml("container", - "<div id=container>abcאבג123</div>"); - - EXPECT_TRUE(RightCharacterOf(0).IsWithinContext()); - EXPECT_EQ(1u, *RightCharacterOf(0).index); - - EXPECT_TRUE(RightCharacterOf(1).IsWithinContext()); - EXPECT_EQ(2u, *RightCharacterOf(1).index); - - EXPECT_TRUE(RightCharacterOf(2).IsWithinContext()); - EXPECT_EQ(6u, *RightCharacterOf(2).index); - - EXPECT_TRUE(RightCharacterOf(3).IsAfterContext()); - - EXPECT_TRUE(RightCharacterOf(4).IsWithinContext()); - EXPECT_EQ(3u, *RightCharacterOf(4).index); - - EXPECT_TRUE(RightCharacterOf(5).IsWithinContext()); - EXPECT_EQ(4u, *RightCharacterOf(5).index); - - EXPECT_TRUE(RightCharacterOf(6).IsWithinContext()); - EXPECT_EQ(7u, *RightCharacterOf(6).index); - - EXPECT_TRUE(RightCharacterOf(7).IsWithinContext()); - EXPECT_EQ(8u, *RightCharacterOf(7).index); - - EXPECT_TRUE(RightCharacterOf(8).IsWithinContext()); - EXPECT_EQ(5u, *RightCharacterOf(8).index); -} - -TEST_F(NGCaretNavigatorTest, LeftPositionOfBasic) { - SetupHtml("container", - "<div id=container>abcאבג123</div>"); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext()); - - EXPECT_TRUE(LeftPositionOf(CaretAfter(0)).IsWithinContext()); - EXPECT_EQ(CaretBefore(0), *LeftPositionOf(CaretAfter(0)).position); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(1)).IsWithinContext()); - EXPECT_EQ(CaretBefore(0), *LeftPositionOf(CaretBefore(1)).position); - - EXPECT_TRUE(LeftPositionOf(CaretAfter(1)).IsWithinContext()); - EXPECT_EQ(CaretBefore(1), *LeftPositionOf(CaretAfter(1)).position); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(2)).IsWithinContext()); - EXPECT_EQ(CaretBefore(1), *LeftPositionOf(CaretBefore(2)).position); - - EXPECT_TRUE(LeftPositionOf(CaretAfter(2)).IsWithinContext()); - EXPECT_EQ(CaretBefore(2), *LeftPositionOf(CaretAfter(2)).position); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(3)).IsWithinContext()); - EXPECT_EQ(CaretAfter(3), *LeftPositionOf(CaretBefore(3)).position); - - EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).IsWithinContext()); - EXPECT_EQ(CaretAfter(4), *LeftPositionOf(CaretAfter(3)).position); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext()); - EXPECT_EQ(CaretAfter(4), *LeftPositionOf(CaretBefore(4)).position); - - EXPECT_TRUE(LeftPositionOf(CaretAfter(4)).IsWithinContext()); - EXPECT_EQ(CaretAfter(5), *LeftPositionOf(CaretAfter(4)).position); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(5)).IsWithinContext()); - EXPECT_EQ(CaretAfter(5), *LeftPositionOf(CaretBefore(5)).position); - - EXPECT_TRUE(LeftPositionOf(CaretAfter(5)).IsWithinContext()); - EXPECT_EQ(CaretBefore(8), *LeftPositionOf(CaretAfter(5)).position); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(6)).IsWithinContext()); - EXPECT_EQ(CaretBefore(2), *LeftPositionOf(CaretBefore(6)).position); - - EXPECT_TRUE(LeftPositionOf(CaretAfter(6)).IsWithinContext()); - EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretAfter(6)).position); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(7)).IsWithinContext()); - EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretBefore(7)).position); - - EXPECT_TRUE(LeftPositionOf(CaretAfter(7)).IsWithinContext()); - EXPECT_EQ(CaretBefore(7), *LeftPositionOf(CaretAfter(7)).position); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(8)).IsWithinContext()); - EXPECT_EQ(CaretBefore(7), *LeftPositionOf(CaretBefore(8)).position); - - EXPECT_TRUE(LeftPositionOf(CaretAfter(8)).IsWithinContext()); - EXPECT_EQ(CaretBefore(8), *LeftPositionOf(CaretAfter(8)).position); -} - -TEST_F(NGCaretNavigatorTest, RightPositionOfBasic) { - SetupHtml("container", - "<div id=container>abcאבג123</div>"); - - EXPECT_TRUE(RightPositionOf(CaretBefore(0)).IsWithinContext()); - EXPECT_EQ(CaretAfter(0), *RightPositionOf(CaretBefore(0)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(0)).IsWithinContext()); - EXPECT_EQ(CaretAfter(1), *RightPositionOf(CaretAfter(0)).position); - - EXPECT_TRUE(RightPositionOf(CaretBefore(1)).IsWithinContext()); - EXPECT_EQ(CaretAfter(1), *RightPositionOf(CaretBefore(1)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(1)).IsWithinContext()); - EXPECT_EQ(CaretAfter(2), *RightPositionOf(CaretAfter(1)).position); - - EXPECT_TRUE(RightPositionOf(CaretBefore(2)).IsWithinContext()); - EXPECT_EQ(CaretAfter(2), *RightPositionOf(CaretBefore(2)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext()); - EXPECT_EQ(CaretAfter(6), *RightPositionOf(CaretAfter(2)).position); - - EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsAfterContext()); - - EXPECT_TRUE(RightPositionOf(CaretAfter(3)).IsWithinContext()); - EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretAfter(3)).position); - - EXPECT_TRUE(RightPositionOf(CaretBefore(4)).IsWithinContext()); - EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretBefore(4)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(4)).IsWithinContext()); - EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(4)).position); - - EXPECT_TRUE(RightPositionOf(CaretBefore(5)).IsWithinContext()); - EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretBefore(5)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(5)).IsWithinContext()); - EXPECT_EQ(CaretBefore(5), *RightPositionOf(CaretAfter(5)).position); - - EXPECT_TRUE(RightPositionOf(CaretBefore(6)).IsWithinContext()); - EXPECT_EQ(CaretAfter(6), *RightPositionOf(CaretBefore(6)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsWithinContext()); - EXPECT_EQ(CaretAfter(7), *RightPositionOf(CaretAfter(6)).position); - - EXPECT_TRUE(RightPositionOf(CaretBefore(7)).IsWithinContext()); - EXPECT_EQ(CaretAfter(7), *RightPositionOf(CaretBefore(7)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(7)).IsWithinContext()); - EXPECT_EQ(CaretAfter(8), *RightPositionOf(CaretAfter(7)).position); - - EXPECT_TRUE(RightPositionOf(CaretBefore(8)).IsWithinContext()); - EXPECT_EQ(CaretAfter(8), *RightPositionOf(CaretBefore(8)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(8)).IsWithinContext()); - EXPECT_EQ(CaretBefore(5), *RightPositionOf(CaretAfter(8)).position); -} - -// Tests below check caret movement crossing line boundaries - -TEST_F(NGCaretNavigatorTest, HardLineBreak) { - SetupHtml("container", "<div id=container>abc<br>def</div>"); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext()); - - EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext()); - EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(2)).position); - - EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext()); - EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretBefore(3)).position); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext()); - EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretBefore(4)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsAfterContext()); -} - -TEST_F(NGCaretNavigatorTest, SoftLineWrapAtSpace) { - SetupHtml("container", "<div id=container style=\"width:0\">abc def</div>"); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext()); - - EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext()); - EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(2)).position); - - EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext()); - EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretBefore(3)).position); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext()); - EXPECT_EQ(CaretAfter(2), *LeftPositionOf(CaretBefore(4)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsAfterContext()); -} - -TEST_F(NGCaretNavigatorTest, BidiAndSoftLineWrapAtSpaceLtr) { - LoadAhem(); - SetupHtml("container", - "<div id=container style='font: 10px/10px Ahem; width: 100px'>" - "before אבגד " - "הוזחטי" - "ךכלםמן" - "נסעףפץ" - "</div>"); - - // Moving left from "|before DCBA" should be before context - EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext()); - - // Moving right from "before |DCBA" should yield "before D|CBA" - EXPECT_TRUE(RightPositionOf(CaretAfter(10)).IsWithinContext()); - EXPECT_EQ(CaretBefore(10), *RightPositionOf(CaretAfter(10)).position); - EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsWithinContext()); - EXPECT_EQ(CaretBefore(10), *RightPositionOf(CaretAfter(6)).position); - - // Moving left from "before |DCBA" should yield "before| DCBA" - EXPECT_TRUE(LeftPositionOf(CaretAfter(10)).IsWithinContext()); - EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretAfter(10)).position); - EXPECT_TRUE(LeftPositionOf(CaretAfter(6)).IsWithinContext()); - EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretAfter(6)).position); - - // Moving right from "before DCBA|" should yield "V|UTSRQPONMLKJIHGFE" - EXPECT_TRUE(RightPositionOf(CaretBefore(7)).IsWithinContext()); - EXPECT_EQ(CaretBefore(29), *RightPositionOf(CaretBefore(7)).position); - - // Moving left from "|VUTSRQPONMLKJIHGFE" should yield "before DCB|A" - EXPECT_TRUE(LeftPositionOf(CaretAfter(29)).IsWithinContext()); - EXPECT_EQ(CaretAfter(7), *LeftPositionOf(CaretAfter(29)).position); - - // Moving right from "VUTSRQPONMLKJIHGFE|" should be after context - EXPECT_TRUE(RightPositionOf(CaretBefore(12)).IsAfterContext()); -} - -TEST_F(NGCaretNavigatorTest, BidiAndSoftLineWrapAtSpaceRtl) { - LoadAhem(); - SetupHtml( - "container", - "<div dir=rtl id=container style='font: 10px/10px Ahem; width: 120px'>" - "אבגד after encyclopedia" - "</div>"); - - // Moving right from "after DCBA|" should be before context - EXPECT_TRUE(RightPositionOf(CaretBefore(0)).IsBeforeContext()); - - // Moving left from "after| DCBA" should yield "afte|r DCBA" - EXPECT_TRUE(LeftPositionOf(CaretAfter(4)).IsWithinContext()); - EXPECT_EQ(CaretBefore(9), *LeftPositionOf(CaretAfter(4)).position); - EXPECT_TRUE(LeftPositionOf(CaretAfter(9)).IsWithinContext()); - EXPECT_EQ(CaretBefore(9), *LeftPositionOf(CaretAfter(9)).position); - - // Moving right from "after| DCBA" should yield "after |DCBA" - EXPECT_TRUE(RightPositionOf(CaretAfter(4)).IsWithinContext()); - EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(4)).position); - EXPECT_TRUE(RightPositionOf(CaretAfter(9)).IsWithinContext()); - EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(9)).position); - - // Moving left from "|after DCBA" should yield "encyclopedi|a" - EXPECT_TRUE(LeftPositionOf(CaretBefore(5)).IsWithinContext()); - EXPECT_EQ(CaretBefore(22), *LeftPositionOf(CaretBefore(5)).position); - - // Moving right from "encyclopedia|" should yield "a|fter DCBA" - EXPECT_TRUE(RightPositionOf(CaretAfter(22)).IsWithinContext()); - EXPECT_EQ(CaretAfter(5), *RightPositionOf(CaretAfter(22)).position); - - // Moving left from "|encyclopedia" should be after context - EXPECT_TRUE(LeftPositionOf(CaretBefore(11)).IsAfterContext()); -} - -TEST_F(NGCaretNavigatorTest, SoftLineWrapAtHyphen) { - SetupHtml("container", "<div id=container style=\"width:0\">abc-def</div>"); - - EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext()); - - // 3 -> 4 - EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext()); - EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretAfter(2)).position); - EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext()); - EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretBefore(3)).position); - - // 4 -> 5 - EXPECT_TRUE(RightPositionOf(CaretAfter(3)).IsWithinContext()); - EXPECT_EQ(CaretAfter(4), *RightPositionOf(CaretAfter(3)).position); - EXPECT_TRUE(RightPositionOf(CaretBefore(4)).IsWithinContext()); - EXPECT_EQ(CaretAfter(4), *RightPositionOf(CaretBefore(4)).position); - - // 5 -> 4 - EXPECT_TRUE(LeftPositionOf(CaretBefore(5)).IsWithinContext()); - EXPECT_EQ(CaretBefore(4), *LeftPositionOf(CaretBefore(5)).position); - EXPECT_TRUE(LeftPositionOf(CaretAfter(4)).IsWithinContext()); - EXPECT_EQ(CaretBefore(4), *LeftPositionOf(CaretAfter(4)).position); - - // 4 -> 3 - EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext()); - EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretBefore(4)).position); - EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).IsWithinContext()); - EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretAfter(3)).position); - - EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsAfterContext()); -} - -TEST_F(NGCaretNavigatorTest, MoveOverPseudoElementInBidi) { - SetupHtml("container", - "<style>.bidi::before,.bidi::after{content:'a\\05D0 b'}</style>" - "<div id=container>אב גד " - "<span class=bidi>הו</span>" - " זח טי</div>"); - - // Text: "AB CD aAbEFaAb GH IJ" - // Rendered as: "DC BA aAbFEaAb JI HG" - - // Moving right from "BA |" should arrive at "F|E" - EXPECT_TRUE(RightPositionOf(CaretAfter(5)).IsWithinContext()); - EXPECT_EQ(CaretBefore(10), *RightPositionOf(CaretAfter(5)).position); - - // Moving left from "|FE" should arrive at "BA| " - EXPECT_TRUE(LeftPositionOf(CaretAfter(10)).IsWithinContext()); - EXPECT_EQ(CaretBefore(5), *LeftPositionOf(CaretAfter(10)).position); - - // Moving right from "FE|" should arrive at " |JI" - EXPECT_TRUE(RightPositionOf(CaretBefore(9)).IsWithinContext()); - EXPECT_EQ(CaretAfter(14), *RightPositionOf(CaretBefore(9)).position); - - // Moving left from "| JI" should arrive at "F|E" - EXPECT_TRUE(LeftPositionOf(CaretBefore(14)).IsWithinContext()); - EXPECT_EQ(CaretAfter(9), *LeftPositionOf(CaretBefore(14)).position); -} - -TEST_F(NGCaretNavigatorTest, EnterableInlineBlock) { - SetupHtml("container", - "<div id=container>foo" - "<span style='display:inline-block'>bar</span>" - "baz</div>"); - - // Moving right from "foo|" should enter the span from front. - EXPECT_TRUE(RightPositionOf(CaretAfter(2)).HasEnteredChildContext()); - EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretAfter(2)).position); - EXPECT_TRUE(RightPositionOf(CaretBefore(3)).HasEnteredChildContext()); - EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretBefore(3)).position); - - // Moving left from "|baz" should enter the span from behind. - EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).HasEnteredChildContext()); - EXPECT_EQ(CaretAfter(3), *LeftPositionOf(CaretBefore(4)).position); - EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).HasEnteredChildContext()); - EXPECT_EQ(CaretAfter(3), *LeftPositionOf(CaretAfter(3)).position); -} - -TEST_F(NGCaretNavigatorTest, UnenterableInlineBlock) { - SetupHtml("container", - "<div id=container>foo" - "<input value=bar>" - "baz</div>"); - - // Moving right from "foo|" should reach "<input>|". - EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext()); - EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretAfter(2)).position); - EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext()); - EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretBefore(3)).position); - - // Moving left from "|baz" should reach "|<input>". - EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext()); - EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretBefore(4)).position); - EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).IsWithinContext()); - EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretAfter(3)).position); -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc index 00c6d5ee3ed..01ea3fc7cdc 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc @@ -19,16 +19,6 @@ namespace blink { namespace { -void AssertValidPositionForCaretPositionComputation( - const PositionWithAffinity& position) { -#if DCHECK_IS_ON() - DCHECK(NGOffsetMapping::AcceptsPosition(position.GetPosition())); - const LayoutObject* layout_object = position.AnchorNode()->GetLayoutObject(); - DCHECK(layout_object); - DCHECK(layout_object->IsText() || layout_object->IsAtomicInlineLevel()); -#endif -} - // The calculation takes the following input: // - An inline formatting context as a |LayoutBlockFlow| // - An offset in the |text_content_| string of the above context @@ -97,7 +87,7 @@ CaretPositionResolution TryResolveCaretPositionInTextFragment( TextAffinity affinity) { const auto& fragment = To<NGPhysicalTextFragment>(paint_fragment.PhysicalFragment()); - if (fragment.IsAnonymousText()) + if (fragment.IsGeneratedText()) return CaretPositionResolution(); const NGOffsetMapping& mapping = @@ -306,7 +296,6 @@ NGCaretPosition ComputeNGCaretPosition(const LayoutBlockFlow& context, } NGCaretPosition ComputeNGCaretPosition(const PositionWithAffinity& position) { - AssertValidPositionForCaretPositionComputation(position); LayoutBlockFlow* context = NGInlineFormattingContextOf(position.GetPosition()); if (!context) diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc index 97c719d8e96..4149569a048 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc @@ -6,8 +6,8 @@ #include "third_party/blink/renderer/core/editing/local_caret_rect.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" #include "third_party/blink/renderer/core/layout/layout_block_flow.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" @@ -16,13 +16,12 @@ namespace blink { namespace { -NGPhysicalOffsetRect ComputeLocalCaretRectByBoxSide( - const NGPaintFragment& fragment, - NGCaretPositionType position_type) { +PhysicalRect ComputeLocalCaretRectByBoxSide(const NGPaintFragment& fragment, + NGCaretPositionType position_type) { const bool is_horizontal = fragment.Style().IsHorizontalWritingMode(); DCHECK(fragment.ContainerLineBox()); const NGPaintFragment& line_box = *fragment.ContainerLineBox(); - const NGPhysicalOffset offset_to_line_box = + const PhysicalOffset offset_to_line_box = fragment.InlineOffsetToContainerBox() - line_box.InlineOffsetToContainerBox(); LayoutUnit caret_height = @@ -48,12 +47,12 @@ NGPhysicalOffsetRect ComputeLocalCaretRectByBoxSide( std::swap(caret_width, caret_height); } - const NGPhysicalOffset caret_location(caret_left, caret_top); - const NGPhysicalSize caret_size(caret_width, caret_height); - return NGPhysicalOffsetRect(caret_location, caret_size); + const PhysicalOffset caret_location(caret_left, caret_top); + const PhysicalSize caret_size(caret_width, caret_height); + return PhysicalRect(caret_location, caret_size); } -NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset( +PhysicalRect ComputeLocalCaretRectAtTextOffset( const NGPaintFragment& paint_fragment, unsigned offset) { const auto& fragment = @@ -81,16 +80,15 @@ NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset( } // Adjust the location to be relative to the inline formatting context. - NGPhysicalOffset caret_location = NGPhysicalOffset(caret_left, caret_top) + - paint_fragment.InlineOffsetToContainerBox(); - NGPhysicalSize caret_size(caret_width, caret_height); + PhysicalOffset caret_location = PhysicalOffset(caret_left, caret_top) + + paint_fragment.InlineOffsetToContainerBox(); + PhysicalSize caret_size(caret_width, caret_height); const NGPaintFragment& context_fragment = *NGPaintFragment::GetForInlineContainer(fragment.GetLayoutObject()); const NGPaintFragment* line_box = paint_fragment.ContainerLineBox(); - const NGPhysicalOffset line_box_offset = - line_box->InlineOffsetToContainerBox(); - const NGPhysicalOffsetRect line_box_rect(line_box_offset, line_box->Size()); + const PhysicalOffset line_box_offset = line_box->InlineOffsetToContainerBox(); + const PhysicalRect line_box_rect(line_box_offset, line_box->Size()); // For horizontal text, adjust the location in the x direction to ensure that // it completely falls in the union of line box and containing block, and @@ -102,7 +100,7 @@ NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset( std::max(context_fragment.Size().width, line_box_rect.Right()); caret_location.left = std::min(caret_location.left, max_x - caret_width); caret_location.left = LayoutUnit(caret_location.left.Round()); - return NGPhysicalOffsetRect(caret_location, caret_size); + return PhysicalRect(caret_location, caret_size); } // Similar adjustment and rounding for vertical text. @@ -112,7 +110,7 @@ NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset( std::max(context_fragment.Size().height, line_box_rect.Bottom()); caret_location.top = std::min(caret_location.top, max_y - caret_height); caret_location.top = LayoutUnit(caret_location.top.Round()); - return NGPhysicalOffsetRect(caret_location, caret_size); + return PhysicalRect(caret_location, caret_size); } LocalCaretRect ComputeLocalCaretRect(const NGCaretPosition& caret_position) { @@ -125,32 +123,21 @@ LocalCaretRect ComputeLocalCaretRect(const NGCaretPosition& caret_position) { case NGCaretPositionType::kBeforeBox: case NGCaretPositionType::kAfterBox: { DCHECK(fragment.PhysicalFragment().IsBox()); - const NGPhysicalOffsetRect fragment_local_rect = - ComputeLocalCaretRectByBoxSide(fragment, - caret_position.position_type); - return {layout_object, fragment_local_rect.ToLayoutRect()}; + const PhysicalRect fragment_local_rect = ComputeLocalCaretRectByBoxSide( + fragment, caret_position.position_type); + return {layout_object, fragment_local_rect}; } case NGCaretPositionType::kAtTextOffset: { DCHECK(fragment.PhysicalFragment().IsText()); DCHECK(caret_position.text_offset.has_value()); - const NGPhysicalOffsetRect caret_rect = ComputeLocalCaretRectAtTextOffset( + const PhysicalRect caret_rect = ComputeLocalCaretRectAtTextOffset( fragment, *caret_position.text_offset); - LayoutRect layout_rect = caret_rect.ToLayoutRect(); - - // For vertical-rl, convert to "flipped block-flow" coordinates space. - // See core/layout/README.md#coordinate-spaces for details. - if (fragment.Style().IsFlippedBlocksWritingMode()) { - const LayoutBlockFlow* container = - layout_object->ContainingNGBlockFlow(); - container->FlipForWritingMode(layout_rect); - } - - return {layout_object, layout_rect}; + return {layout_object, caret_rect}; } } NOTREACHED(); - return {layout_object, LayoutRect()}; + return {layout_object, PhysicalRect()}; } LocalCaretRect ComputeLocalSelectionRect( @@ -159,32 +146,21 @@ LocalCaretRect ComputeLocalSelectionRect( if (!caret_rect.layout_object) return caret_rect; - const LayoutObject* layout_object = caret_rect.layout_object; - const LayoutRect rect = caret_rect.rect; - const NGPaintFragment& fragment = *caret_position.fragment; const NGPaintFragment* line_box = fragment.ContainerLineBox(); // TODO(xiaochengh): We'll hit this DCHECK for caret in empty block if we // enable LayoutNG in contenteditable. DCHECK(line_box); + PhysicalRect rect = caret_rect.rect; if (fragment.Style().IsHorizontalWritingMode()) { - const LayoutUnit line_top = line_box->InlineOffsetToContainerBox().top; - const LayoutUnit line_height = line_box->Size().height; - return LocalCaretRect(layout_object, LayoutRect(rect.X(), line_top, - rect.Width(), line_height)); - } - - const LayoutUnit line_top = line_box->InlineOffsetToContainerBox().left; - const LayoutUnit line_height = line_box->Size().width; - LayoutRect layout_rect(line_top, rect.Y(), line_height, rect.Height()); - // For vertical-rl, convert to "flipped block-flow" coordinates space. - // See core/layout/README.md#coordinate-spaces for details. - if (fragment.Style().IsFlippedBlocksWritingMode()) { - const LayoutBlockFlow* container = layout_object->ContainingNGBlockFlow(); - container->FlipForWritingMode(layout_rect); + rect.SetY(line_box->InlineOffsetToContainerBox().top); + rect.SetHeight(line_box->Size().height); + } else { + rect.SetX(line_box->InlineOffsetToContainerBox().left); + rect.SetHeight(line_box->Size().width); } - return LocalCaretRect(layout_object, layout_rect); + return {caret_rect.layout_object, rect}; } } // namespace diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.cc new file mode 100644 index 00000000000..61a0ecf53b5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.cc @@ -0,0 +1,50 @@ +// 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/inline/ng_dirty_lines.h" + +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" + +namespace blink { + +void NGDirtyLines::MarkLastFragment() { + if (last_fragment_) { + // Changes in this LayoutObject may affect the line that contains its + // previous object. Mark the line box that contains the last fragment + // of the previous object. + last_fragment_->LastForSameLayoutObject()->MarkContainingLineBoxDirty(); + } else { + // If there were no fragments so far in this pre-order traversal, mark + // the first line box dirty. + DCHECK(block_fragment_); + if (NGPaintFragment* first_line = block_fragment_->FirstLineBox()) + first_line->MarkLineBoxDirty(); + } +} + +void NGDirtyLines::MarkAtTextOffset(unsigned offset) { + for (NGPaintFragment* child : block_fragment_->Children()) { + // Only the first dirty line is relevant. + if (child->IsDirty()) + break; + + const auto* line = + DynamicTo<NGPhysicalLineBoxFragment>(child->PhysicalFragment()); + if (!line) + continue; + + const auto* break_token = To<NGInlineBreakToken>(line->BreakToken()); + DCHECK(break_token); + if (break_token->IsFinished()) + break; + + if (offset < break_token->TextOffset()) { + child->MarkLineBoxDirty(); + break; + } + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h new file mode 100644 index 00000000000..5e97423cd95 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h @@ -0,0 +1,91 @@ +// 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_DIRTY_LINES_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_DIRTY_LINES_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/layout_box.h" +#include "third_party/blink/renderer/core/layout/layout_inline.h" +#include "third_party/blink/renderer/core/layout/layout_text.h" +#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" + +namespace blink { + +// This class computes dirty line boxes. +class CORE_EXPORT NGDirtyLines { + STACK_ALLOCATED(); + + public: + explicit NGDirtyLines(const NGPaintFragment* block_fragment) + : block_fragment_(block_fragment) { + DCHECK(block_fragment_); + } + + // Call |Handle*| functions for each object by traversing the LayoutObject + // tree in pre-order DFS. + // + // They return |true| when a dirty line was found. Because only the first + // dirty line is relevant, no further calls are necessary. + bool HandleText(LayoutText* layout_text) { + if (layout_text->SelfNeedsLayout()) { + MarkLastFragment(); + return true; + } + UpdateLastFragment(layout_text->FirstInlineFragment()); + return false; + } + + bool HandleInlineBox(LayoutInline* layout_inline) { + if (layout_inline->SelfNeedsLayout()) { + MarkLastFragment(); + return true; + } + // Do not keep fragments of LayoutInline unless it's a leaf, because + // the last fragment of LayoutInline is not the previous fragment of its + // descendants. + if (UNLIKELY(!layout_inline->FirstChild())) + UpdateLastFragment(layout_inline->FirstInlineFragment()); + return false; + } + + bool HandleAtomicInline(LayoutBox* layout_box) { + if (layout_box->NeedsLayout()) { + MarkLastFragment(); + return true; + } + UpdateLastFragment(layout_box->FirstInlineFragment()); + return false; + } + + bool HandleFloatingOrOutOfFlowPositioned(LayoutObject* layout_object) { + DCHECK(layout_object->IsFloatingOrOutOfFlowPositioned()); + if (layout_object->NeedsLayout()) { + MarkLastFragment(); + return true; + } + // Don't update last fragment. Floats and OOF are opaque. + return false; + } + + // Mark the line box at the specified text offset dirty. + void MarkAtTextOffset(unsigned offset); + + private: + void UpdateLastFragment(NGPaintFragment* fragment) { + if (fragment) + last_fragment_ = fragment; + } + + // Mark the line box that contains |last_fragment_| dirty. If |last_fragment_| + // is |nullptr|, the first line box is marked as dirty. + void MarkLastFragment(); + + const NGPaintFragment* block_fragment_; + NGPaintFragment* last_fragment_ = nullptr; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_DIRTY_LINES_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc index 1899fd16482..3fa122f9709 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc @@ -4,8 +4,8 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h" @@ -254,8 +254,8 @@ void NGInlineLayoutStateStack::AddBoxFragmentPlaceholder( DCHECK(box->style); const ComputedStyle& style = *box->style; - NGLogicalOffset offset; - NGLogicalSize size; + LogicalOffset offset; + LogicalSize size; if (!is_empty_line_) { // The inline box should have the height of the font metrics without the // line-height property. Compute from style because |box->metrics| includes @@ -394,12 +394,15 @@ unsigned NGInlineLayoutStateStack::UpdateBoxDataFragmentRange( const unsigned box_data_index = start->box_data_index; if (!box_data_index) continue; + // |box_data_list_[box_data_index - 1]| is the box for |start| child. + // Avoid keeping a pointer to the |BoxData| because it maybe invalidated as + // we add to |box_data_list_|. // As |box_data_index| is converted to start/end of BoxData, update // |box_data_index| to the parent box, or to 0 if no parent boxes. // This allows including this box to the nested parent box. - BoxData* box_data = &box_data_list_[box_data_index - 1]; - start->box_data_index = box_data->parent_box_data_index; + start->box_data_index = + box_data_list_[box_data_index - 1].parent_box_data_index; // Find the end line box item. const unsigned start_index = index; @@ -415,27 +418,33 @@ unsigned NGInlineLayoutStateStack::UpdateBoxDataFragmentRange( // because the update is limited only when its |box_data_index| is lower. while (end->box_data_index && end->box_data_index < box_data_index) { UpdateBoxDataFragmentRange(line_box, index); - // Re-compute |box_data| in case |box_data_list_| was reallocated when - // |UpdateBoxDataFragmentRange| added new fragments. - box_data = &box_data_list_[box_data_index - 1]; } if (box_data_index != end->box_data_index) break; - end->box_data_index = box_data->parent_box_data_index; + end->box_data_index = + box_data_list_[box_data_index - 1].parent_box_data_index; } // If this is the first range for this BoxData, set it. - if (!box_data->fragment_end) { - box_data->fragment_start = start_index; - box_data->fragment_end = index; + if (!box_data_list_[box_data_index - 1].fragment_end) { + box_data_list_[box_data_index - 1].SetFragmentRange(start_index, index); } else { // This box is fragmented by BiDi reordering. Add a new BoxData for the // fragmented range. - box_data->fragmented_box_data_index = box_data_list_.size(); - box_data_list_.emplace_back(*box_data, start_index, index); + box_data_list_[box_data_index - 1].fragmented_box_data_index = + box_data_list_.size(); + // Do not use `emplace_back()` here because adding to |box_data_list_| may + // reallocate the buffer, but the `BoxData` ctor must run before the + // reallocation. Create a new instance and |push_back()| instead. + BoxData fragmented_box_data(box_data_list_[box_data_index - 1], + start_index, index); + box_data_list_.push_back(fragmented_box_data); } - return box_data->parent_box_data_index ? start_index : index; + // If this box has parent boxes, we need to process it again. + if (box_data_list_[box_data_index - 1].parent_box_data_index) + return start_index; + return index; } return index; } @@ -572,10 +581,18 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( DCHECK(item); DCHECK(item->Style()); const ComputedStyle& style = *item->Style(); + + NGFragmentGeometry fragment_geometry; + fragment_geometry.border_box_size = size; + fragment_geometry.border_box_size.inline_size.ClampNegativeToZero(); + fragment_geometry.padding = + NGBoxStrut(padding, IsFlippedLinesWritingMode(style.GetWritingMode())); + // Because children are already in the visual order, use LTR for the // fragment builder so that it should not transform the coordinates for RTL. NGBoxFragmentBuilder box(item->GetLayoutObject(), &style, style.GetWritingMode(), TextDirection::kLtr); + box.SetInitialFragmentGeometry(fragment_geometry); box.SetBoxType(NGPhysicalFragment::kInlineBox); box.SetStyleVariant(item->StyleVariant()); @@ -583,14 +600,12 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( // was fragmented. Fragmenting a line box in block direction is not // supported today. box.SetBorderEdges({true, has_line_right_edge, true, has_line_left_edge}); - box.SetInlineSize(size.inline_size.ClampNegativeToZero()); - box.SetBlockSize(size.block_size); - box.SetPadding(padding); for (unsigned i = fragment_start; i < fragment_end; i++) { NGLineBoxFragmentBuilder::Child& child = (*line_box)[i]; if (child.layout_result) { - box.AddChild(*child.layout_result, child.offset - offset); + box.AddChild(child.layout_result->PhysicalFragment(), + child.offset - offset); child.layout_result.reset(); } else if (child.fragment) { box.AddChild(std::move(child.fragment), child.offset - offset); @@ -601,7 +616,7 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( // child.offset is the static position wrt. the linebox. As we are adding // this as a child of an inline level fragment, we adjust the static // position to be relative to this fragment. - NGLogicalOffset static_offset = child.offset - offset; + LogicalOffset static_offset = child.offset - offset; box.AddOutOfFlowChildCandidate(oof_box, static_offset, child.container_direction); @@ -609,6 +624,10 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( } } + // Inline boxes that produce DisplayItemClient should do full paint + // invalidations. + item->GetLayoutObject()->SetShouldDoFullPaintInvalidation(); + box.MoveOutOfFlowDescendantCandidatesToDescendants(); return box.ToInlineBoxFragment(); } @@ -747,10 +766,14 @@ NGLineHeightMetrics NGInlineLayoutStateStack::MetricsForTopAndBottomAlign( // BoxData contains inline boxes to be created later. Take them into account. for (const BoxData& box_data : box_data_list_) { + // |block_offset| is the top position when the baseline is at 0. LayoutUnit box_ascent = -line_box[box_data.fragment_end].offset.block_offset; - metrics.Unite( - NGLineHeightMetrics(box_ascent, box_data.size.block_size - box_ascent)); + LayoutUnit box_descent = box_data.size.block_size - box_ascent; + // The top/bottom of inline boxes should not include their paddings. + box_ascent -= box_data.padding.line_over; + box_descent -= box_data.padding.line_under; + metrics.Unite(NGLineHeightMetrics(box_ascent, box_descent)); } // In quirks mode, metrics is empty if no content. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h index f6ea8e18c77..2c6d9afb49d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h @@ -5,7 +5,7 @@ #ifndef NGInlineBoxState_h #define NGInlineBoxState_h -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h" #include "third_party/blink/renderer/core/style/computed_style_constants.h" @@ -106,7 +106,7 @@ struct NGInlineBoxState { // 2) Performs layout when the positin/size of a box was computed. // 3) Cache common values for a box. class CORE_EXPORT NGInlineLayoutStateStack { - STACK_ALLOCATED(); + DISALLOW_NEW(); public: // The box state for the line box. @@ -204,7 +204,7 @@ class CORE_EXPORT NGInlineLayoutStateStack { BoxData(unsigned start, unsigned end, const NGInlineItem* item, - NGLogicalSize size) + LogicalSize size) : fragment_start(start), fragment_end(end), item(item), size(size) {} BoxData(const BoxData& other, unsigned start, unsigned end) @@ -214,12 +214,17 @@ class CORE_EXPORT NGInlineLayoutStateStack { size(other.size), offset(other.offset) {} + void SetFragmentRange(unsigned start_index, unsigned end_index) { + fragment_start = start_index; + fragment_end = end_index; + } + // The range of child fragments this box contains. unsigned fragment_start; unsigned fragment_end; const NGInlineItem* item; - NGLogicalSize size; + LogicalSize size; bool has_line_left_edge = false; bool has_line_right_edge = false; @@ -230,7 +235,7 @@ class CORE_EXPORT NGInlineLayoutStateStack { LayoutUnit margin_border_padding_line_left; LayoutUnit margin_border_padding_line_right; - NGLogicalOffset offset; + LogicalOffset offset; unsigned parent_box_data_index = 0; unsigned fragmented_box_data_index = 0; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc index 23f9cdbfa28..3a9b6f7814d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc @@ -41,7 +41,7 @@ NGInlineBreakToken::NGInlineBreakToken(NGLayoutInputNode node) NGInlineBreakToken::~NGInlineBreakToken() = default; -#ifndef NDEBUG +#if DCHECK_IS_ON() String NGInlineBreakToken::ToString() const { StringBuilder string_builder; @@ -55,6 +55,6 @@ String NGInlineBreakToken::ToString() const { return string_builder.ToString(); } -#endif // NDEBUG +#endif // DCHECK_IS_ON() } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h index f457fe494e2..ba6599469b6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h @@ -78,9 +78,9 @@ class CORE_EXPORT NGInlineBreakToken final : public NGBreakToken { void SetIgnoreFloats() { ignore_floats_ = true; } bool IgnoreFloats() const { return ignore_floats_; } -#ifndef NDEBUG +#if DCHECK_IS_ON() String ToString() const override; -#endif // NDEBUG +#endif private: NGInlineBreakToken(NGInlineNode node, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h index 059776134d3..5992007f223 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h @@ -18,7 +18,7 @@ class NGInlineItem; // Because this context is in initial state for when fragmentation occurs and // some other cases, do not add things that are too expensive to rebuild. class NGInlineChildLayoutContext { - STACK_ALLOCATED(); + DISALLOW_NEW(); public: // Returns the NGInlineLayoutStateStack in this context. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc index 9c4720ab64e..5e20fc2a028 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc @@ -72,7 +72,7 @@ class NGPhysicalFragmentCollectorBase { for (const auto& child : To<NGPhysicalContainerFragment>(fragment).Children()) { - base::AutoReset<NGPhysicalOffset> offset_resetter( + base::AutoReset<PhysicalOffset> offset_resetter( ¤t_offset_to_root_, current_offset_to_root_ + child.Offset()); base::AutoReset<const NGPhysicalFragment*> fragment_resetter( ¤t_fragment_, child.get()); @@ -86,7 +86,7 @@ class NGPhysicalFragmentCollectorBase { private: const NGPhysicalFragment* root_fragment_ = nullptr; const NGPhysicalFragment* current_fragment_ = nullptr; - NGPhysicalOffset current_offset_to_root_; + PhysicalOffset current_offset_to_root_; Vector<Result> results_; bool should_stop_traversing_ = false; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc index 77cfcafd79e..801aaf305c2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc @@ -110,6 +110,38 @@ unsigned NGLineInfo::InflowEndOffset() const { return StartOffset(); } +bool NGLineInfo::ShouldHangTrailingSpaces() const { + DCHECK(HasTrailingSpaces()); + if (!line_style_->AutoWrap()) + return false; + switch (text_align_) { + case ETextAlign::kStart: + case ETextAlign::kJustify: + return true; + case ETextAlign::kEnd: + case ETextAlign::kCenter: + case ETextAlign::kWebkitCenter: + return false; + case ETextAlign::kLeft: + case ETextAlign::kWebkitLeft: + return IsLtr(BaseDirection()); + case ETextAlign::kRight: + case ETextAlign::kWebkitRight: + return IsRtl(BaseDirection()); + } + NOTREACHED(); +} + +void NGLineInfo::UpdateTextAlign() { + text_align_ = line_style_->GetTextAlign(IsLastLine()); + + if (HasTrailingSpaces() && ShouldHangTrailingSpaces()) { + hang_width_ = ComputeTrailingSpaceWidth(&end_offset_for_justify_); + } else if (text_align_ == ETextAlign::kJustify) { + end_offset_for_justify_ = InflowEndOffset(); + } +} + LayoutUnit NGLineInfo::ComputeTrailingSpaceWidth( unsigned* end_offset_out) const { if (!has_trailing_spaces_) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h index 9e0253f842b..505bf23e4d6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h @@ -176,17 +176,22 @@ class CORE_EXPORT NGLineInfo { void SetTextIndent(LayoutUnit indent) { text_indent_ = indent; } LayoutUnit TextIndent() const { return text_indent_; } + ETextAlign TextAlign() const { return text_align_; } + // Update |TextAlign()| and related fields. This depends on |IsLastLine()| and + // that must be called after |SetIsLastLine()|. + void UpdateTextAlign(); + NGBfcOffset BfcOffset() const { return bfc_offset_; } LayoutUnit AvailableWidth() const { return available_width_; } // The width of this line. Includes trailing spaces if they were preserved. // Negative width created by negative 'text-indent' is clamped to zero. LayoutUnit Width() const { return width_.ClampNegativeToZero(); } - // Same as |Width()| but returns negative value as is. - LayoutUnit WidthForAlignment() const { return width_; } - // The width of preserved trailing spaces. - LayoutUnit ComputeTrailingSpaceWidth( - unsigned* end_offset_out = nullptr) const; + // Same as |Width()| but returns negative value as is. Preserved trailing + // spaces may or may not be included, depends on |ShouldHangTrailingSpaces()|. + LayoutUnit WidthForAlignment() const { return width_ - hang_width_; } + // Width that hangs over the end of the line; e.g., preserved trailing spaces. + LayoutUnit HangWidth() const { return hang_width_; } // Compute |Width()| from |Results()|. Used during line breaking, before // |Width()| is set. After line breaking, this should match to |Width()| // without clamping. @@ -194,6 +199,7 @@ class CORE_EXPORT NGLineInfo { bool HasTrailingSpaces() const { return has_trailing_spaces_; } void SetHasTrailingSpaces() { has_trailing_spaces_ = true; } + bool ShouldHangTrailingSpaces() const; // True if this line has overflow, excluding preserved trailing spaces. bool HasOverflow() const { return has_overflow_; } @@ -211,6 +217,12 @@ class CORE_EXPORT NGLineInfo { // End text offset of this line, excluding out-of-flow objects such as // floating or positioned. unsigned InflowEndOffset() const; + // End text offset for `text-align: justify`. This excludes preserved trailing + // spaces. Available only when |TextAlign()| is |kJustify|. + unsigned EndOffsetForJustify() const { + DCHECK_EQ(text_align_, ETextAlign::kJustify); + return end_offset_for_justify_; + } // End item index of this line. unsigned EndItemIndex() const { return end_item_index_; } void SetEndItemIndex(unsigned index) { end_item_index_ = index; } @@ -234,6 +246,10 @@ class CORE_EXPORT NGLineInfo { private: bool ComputeNeedsAccurateEndPosition() const; + // The width of preserved trailing spaces. + LayoutUnit ComputeTrailingSpaceWidth( + unsigned* end_offset_out = nullptr) const; + const NGInlineItemsData* items_data_ = nullptr; const ComputedStyle* line_style_ = nullptr; NGInlineItemResults results_; @@ -243,11 +259,14 @@ class CORE_EXPORT NGLineInfo { LayoutUnit available_width_; LayoutUnit width_; + LayoutUnit hang_width_; LayoutUnit text_indent_; unsigned start_offset_; unsigned end_item_index_; + unsigned end_offset_for_justify_; + ETextAlign text_align_ = ETextAlign::kLeft; TextDirection base_direction_ = TextDirection::kLtr; bool use_first_line_style_ = false; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc index d05f2ce5e12..e881df5256a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc @@ -9,7 +9,9 @@ #include "third_party/blink/renderer/core/layout/layout_inline.h" #include "third_party/blink/renderer/core/layout/layout_text.h" #include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.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_offset_mapping_builder.h" #include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" @@ -150,6 +152,8 @@ inline bool IsCollapsibleSpace(UChar c) { // It makes the line breaker easier to handle. inline bool IsControlItemCharacter(UChar c) { return c == kNewlineCharacter || c == kTabulationCharacter || + // Make ZWNJ a control character so that it can prevent kerning. + c == kZeroWidthNonJoinerCharacter || // Include ignorable character here to avoids shaping/rendering // these glyphs, and to help the line breaker to ignore them. ShouldIgnore(c); @@ -275,7 +279,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>:: template <typename OffsetMappingBuilder> bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextReusing( - const String& original_string, + const NGInlineNodeData& original_data, LayoutText* layout_text) { DCHECK(layout_text); const NGInlineItems& items = layout_text->InlineItems(); @@ -283,6 +287,8 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextReusing( if (!old_item0.Length()) return false; + const String& original_string = original_data.text_content; + // Don't reuse existing items if they might be affected by whitespace // collapsing. // TODO(layout-dev): This could likely be optimized further. @@ -311,12 +317,26 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextReusing( } break; case NGInlineItem::kNotCollapsible: { - // If the start of the original string was collapsed, it may be - // restored. const String& source_text = layout_text->GetText(); - if (source_text.length() && IsCollapsibleSpace(source_text[0]) && - original_string[old_item0.StartOffset()] != kSpaceCharacter) - return false; + if (source_text.length() && IsCollapsibleSpace(source_text[0])) { + // If the start of the original string was collapsed, it may be + // restored. + if (original_string[old_item0.StartOffset()] != kSpaceCharacter) + return false; + // If the start of the original string was not collapsed, and the + // collapsible space run contains newline, the newline may be + // removed. + unsigned offset = 0; + UChar c = source_text[0]; + bool contains_newline = + MoveToEndOfCollapsibleSpaces(source_text, &offset, &c); + if (contains_newline && + ShouldRemoveNewline(text_, text_.length(), last_item->Style(), + StringView(source_text, offset), + &new_style)) { + return false; + } + } break; } case NGInlineItem::kCollapsed: @@ -340,12 +360,22 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextReusing( return false; } - if (bidi_context_.size() && new_style.PreserveNewline()) { - // We exit and then re-enter all bidi contexts around a forced breaks. We + if (new_style.PreserveNewline()) { + // We exit and then re-enter all bidi contexts around a forced break. So, We // must go through the full pipeline to ensure that we exit and enter the - // contexts in the same in the re-layout. - if (layout_text->GetText().Contains(kNewlineCharacter)) - return false; + // correct bidi contexts the re-layout. + if (bidi_context_.size() || layout_text->HasBidiControlInlineItems()) { + if (layout_text->GetText().Contains(kNewlineCharacter)) + return false; + } + } + + if (UNLIKELY(old_item0.StartOffset() > 0 && + ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces( + layout_text->GetText(), new_style))) { + // e.g. <p>abc xyz</p> => <p> xyz</p> where "abc" and " xyz" are different + // Text node. |text_| is " \u200Bxyz". + return false; } for (const NGInlineItem& item : items) { @@ -398,7 +428,7 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextReusing( template <> bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::AppendTextReusing( - const String&, + const NGInlineNodeData&, LayoutText*) { NOTREACHED(); return false; @@ -406,6 +436,30 @@ bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::AppendTextReusing( template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendText( + LayoutText* layout_text, + const NGInlineNodeData* previous_data) { + // Mark dirty lines. Clear if marked, only the first dirty line is relevant. + if (dirty_lines_ && dirty_lines_->HandleText(layout_text)) + dirty_lines_ = nullptr; + + // If the LayoutText element hasn't changed, reuse the existing items. + if (previous_data && layout_text->HasValidInlineItems()) { + if (AppendTextReusing(*previous_data, layout_text)) { + return; + } + } + + // If not create a new item as needed. + if (UNLIKELY(layout_text->IsWordBreak())) { + AppendBreakOpportunity(layout_text); + return; + } + + AppendText(layout_text->GetText(), layout_text); +} + +template <typename OffsetMappingBuilder> +void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendText( const String& string, LayoutText* layout_object) { DCHECK(layout_object); @@ -623,6 +677,18 @@ void NGInlineItemsBuilderTemplate< is_empty_inline_ = false; // text item is not empty. } +template <typename OffsetMappingBuilder> +bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>:: + ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces( + const String& string, + const ComputedStyle& style) const { + return text_.IsEmpty() && string.length() > 0 && + string[0] == kSpaceCharacter && !style.CollapseWhiteSpace() && + style.AutoWrap(); +} + +// TODO(yosin): We should remove |style| and |string| parameter because of +// except for testing, we can get them from |LayoutText|. // Even when without whitespace collapsing, control characters (newlines and // tabs) are in their own control items to make the line breaker not special. template <typename OffsetMappingBuilder> @@ -638,8 +704,8 @@ void NGInlineItemsBuilderTemplate< // opportunity after leading preserved spaces needs a special code in the line // breaker. Generate an opportunity to make it easy. unsigned start = 0; - if (UNLIKELY(text_.IsEmpty() && string[start] == kSpaceCharacter && - style->AutoWrap())) { + if (UNLIKELY(ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces( + string, *style))) { do { ++start; } while (start < string.length() && string[start] == kSpaceCharacter); @@ -653,7 +719,9 @@ void NGInlineItemsBuilderTemplate< if (c == kNewlineCharacter) { AppendForcedBreak(layout_object); start++; - } else if (c == kTabulationCharacter) { + continue; + } + if (c == kTabulationCharacter) { wtf_size_t end = string.Find( [](UChar c) { return c != kTabulationCharacter; }, start + 1); if (end == kNotFound) @@ -661,11 +729,14 @@ void NGInlineItemsBuilderTemplate< AppendTextItem(NGInlineItem::kControl, StringView(string, start, end - start), layout_object); start = end; - } else { + continue; + } + // ZWNJ splits item, but it should be text. + if (c != kZeroWidthNonJoinerCharacter) { Append(NGInlineItem::kControl, c, layout_object); start++; + continue; } - continue; } wtf_size_t end = string.Find(IsControlItemCharacter, start + 1); @@ -773,6 +844,11 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendAtomicInline( Append(NGInlineItem::kAtomicInline, kObjectReplacementCharacter, layout_object); + // Mark dirty lines. Clear if marked, only the first dirty line is relevant. + if (dirty_lines_ && + dirty_lines_->HandleAtomicInline(ToLayoutBox(layout_object))) + dirty_lines_ = nullptr; + // When this atomic inline is inside of an inline box, the height of the // inline box can be different from the height of the atomic inline. Ensure // the inline box creates a box fragment so that its height is available in @@ -787,17 +863,25 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendAtomicInline( template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendFloating( LayoutObject* layout_object) { - changes_may_affect_earlier_lines_ = true; AppendOpaque(NGInlineItem::kFloating, kObjectReplacementCharacter, layout_object); + + // Mark dirty lines. Clear if marked, only the first dirty line is relevant. + if (dirty_lines_ && + dirty_lines_->HandleFloatingOrOutOfFlowPositioned(layout_object)) + dirty_lines_ = nullptr; } template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>:: AppendOutOfFlowPositioned(LayoutObject* layout_object) { - changes_may_affect_earlier_lines_ = true; AppendOpaque(NGInlineItem::kOutOfFlowPositioned, kObjectReplacementCharacter, layout_object); + + // Mark dirty lines. Clear if marked, only the first dirty line is relevant. + if (dirty_lines_ && + dirty_lines_->HandleFloatingOrOutOfFlowPositioned(layout_object)) + dirty_lines_ = nullptr; } template <typename OffsetMappingBuilder> @@ -857,6 +941,12 @@ void NGInlineItemsBuilderTemplate< text_.erase(space_offset); mapping_builder_.CollapseTrailingSpace(space_offset); + // Mark dirty lines. Clear if marked, only the first dirty line is relevant. + if (dirty_lines_) { + dirty_lines_->MarkAtTextOffset(space_offset); + dirty_lines_ = nullptr; + } + // Keep the item even if the length became zero. This is not needed for // the layout purposes, but needed to maintain LayoutObject states. See // |AddEmptyTextItem()|. @@ -972,7 +1062,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::EnterBlock( template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::EnterInline( - LayoutObject* node) { + LayoutInline* node) { DCHECK(node); // https://drafts.csswg.org/css-writing-modes-3/#bidi-control-codes-injection-table @@ -1013,6 +1103,10 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::EnterInline( AppendOpaque(NGInlineItem::kOpenTag, node); + // Mark dirty lines. Clear if marked, only the first dirty line is relevant. + if (dirty_lines_ && dirty_lines_->HandleInlineBox(node)) + dirty_lines_ = nullptr; + if (!NeedsBoxInfo()) return; @@ -1072,7 +1166,8 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::SetIsSymbolMarker( template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::ClearInlineFragment( LayoutObject* object) { - NGInlineNode::ClearInlineFragment(object); + object->SetIsInLayoutNGInlineFormattingContext(true); + object->SetFirstInlineFragment(nullptr); } template <typename OffsetMappingBuilder> diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h index 9d04bba9549..b7c9530edd1 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h @@ -21,6 +21,8 @@ class ComputedStyle; class LayoutInline; class LayoutObject; class LayoutText; +struct NGInlineNodeData; +class NGDirtyLines; // NGInlineItemsBuilder builds a string and a list of NGInlineItem from inlines. // @@ -42,8 +44,13 @@ class NGInlineItemsBuilderTemplate { STACK_ALLOCATED(); public: - explicit NGInlineItemsBuilderTemplate(Vector<NGInlineItem>* items) - : items_(items) {} + // Create a builder that appends items to |items|. + // + // If |dirty_lines| is given, this builder calls its functions to mark lines + // dirty. + explicit NGInlineItemsBuilderTemplate(Vector<NGInlineItem>* items, + NGDirtyLines* dirty_lines = nullptr) + : items_(items), dirty_lines_(dirty_lines) {} ~NGInlineItemsBuilderTemplate(); String ToString(); @@ -62,11 +69,19 @@ class NGInlineItemsBuilderTemplate { return changes_may_affect_earlier_lines_; } + // Append a string from |LayoutText|. + // + // If |previous_data| is given, reuse existing items if they exist and are + // reusable. Otherwise appends new items. + void AppendText(LayoutText* layout_text, + const NGInlineNodeData* previous_data); + // Append existing items from an unchanged LayoutObject. // Returns whether the existing items could be reused. // NOTE: The state of the builder remains unchanged if the append operation // fails (i.e. if it returns false). - bool AppendTextReusing(const String& previous_text, LayoutText* layout_text); + bool AppendTextReusing(const NGInlineNodeData& previous_data, + LayoutText* layout_text); // Append a string. // When appending, spaces are collapsed according to CSS Text, The white space @@ -114,7 +129,7 @@ class NGInlineItemsBuilderTemplate { void EnterBlock(const ComputedStyle*); void ExitBlock(); - void EnterInline(LayoutObject*); + void EnterInline(LayoutInline*); void ExitInline(LayoutObject*); OffsetMappingBuilder& GetOffsetMappingBuilder() { return mapping_builder_; } @@ -134,6 +149,8 @@ class NGInlineItemsBuilderTemplate { Vector<NGInlineItem>* items_; StringBuilder text_; + NGDirtyLines* dirty_lines_; + // |mapping_builder_| builds the whitespace-collapsed offset mapping // during inline collection. It is updated whenever |text_| is modified or a // white space is collapsed. @@ -200,11 +217,17 @@ class NGInlineItemsBuilderTemplate { void AppendGeneratedBreakOpportunity(LayoutObject*); void Exit(LayoutObject*); + + bool ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces( + const String&, + const ComputedStyle&) const; }; template <> -CORE_EXPORT bool NGInlineItemsBuilderTemplate< - NGOffsetMappingBuilder>::AppendTextReusing(const String&, LayoutText*); +CORE_EXPORT bool +NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::AppendTextReusing( + const NGInlineNodeData&, + LayoutText*); template <> CORE_EXPORT void diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc index 868bbe90a35..56fa24daadc 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc @@ -7,13 +7,12 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/core/layout/layout_inline.h" #include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" #include "third_party/blink/renderer/core/style/computed_style.h" namespace blink { -namespace { - // The spec turned into a discussion that may change. Put this logic on hold // until CSSWG resolves the issue. // https://github.com/w3c/csswg-drafts/issues/337 @@ -79,7 +78,7 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { builder.ExitBlock(); text_ = builder.ToString(); ValidateItems(); - CheckReuseItemsProducesSameResult(inputs); + CheckReuseItemsProducesSameResult(inputs, builder.HasBidiControls()); for (LayoutObject* anonymous_object : anonymous_objects) anonymous_object->Destroy(); return text_; @@ -111,7 +110,12 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { EXPECT_EQ(current_offset, text_.length()); } - void CheckReuseItemsProducesSameResult(Vector<Input> inputs) { + void CheckReuseItemsProducesSameResult(Vector<Input> inputs, + bool has_bidi_controls) { + NGInlineNodeData fake_data; + fake_data.text_content = text_; + fake_data.is_bidi_enabled_ = has_bidi_controls; + Vector<NGInlineItem> reuse_items; NGInlineItemsBuilder reuse_builder(&reuse_items); for (Input& input : inputs) { @@ -131,8 +135,9 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { } // Try to re-use previous items, or Append if it was not re-usable. - bool reused = input.layout_text->HasValidInlineItems() && - reuse_builder.AppendTextReusing(text_, input.layout_text); + bool reused = + input.layout_text->HasValidInlineItems() && + reuse_builder.AppendTextReusing(fake_data, input.layout_text); if (!reused) { reuse_builder.AppendText(input.text, input.layout_text); } @@ -493,6 +498,4 @@ TEST_F(NGInlineItemsBuilderTest, BidiIsolateOverride) { builder.ToString()); } -} // namespace - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc index c8c01e84f7c..f3d4a644b8c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc @@ -96,8 +96,10 @@ NGInlineBoxState* NGInlineLayoutAlgorithm::HandleCloseTag( box->EnsureTextMetrics(*item.Style(), baseline_type_); box = box_states_->OnCloseTag(&line_box_, box, baseline_type_, item.HasEndEdge()); - item.GetLayoutObject()->SetShouldDoFullPaintInvalidation(); - ClearNeedsLayoutIfNeeded(item.GetLayoutObject()); + // Just clear |NeedsLayout| flags. Culled inline boxes do not need paint + // invalidations. If this object produces box fragments, + // |NGInlineBoxStateStack| takes care of invalidations. + item.GetLayoutObject()->ClearNeedsLayoutWithoutPaintInvalidation(); return box; } @@ -239,7 +241,8 @@ void NGInlineLayoutAlgorithm::CreateLine( } line_box_.AddChild(text_builder.ToTextFragment(), box->text_top, item_result.inline_size, item.BidiLevel()); - ClearNeedsLayoutIfNeeded(item.GetLayoutObject()); + // Text boxes always need full paint invalidations. + item.GetLayoutObject()->ClearNeedsLayoutWithFullPaintInvalidation(); } else if (item.Type() == NGInlineItem::kControl) { PlaceControlItem(item, *line_info, &item_result, box); } else if (item.Type() == NGInlineItem::kOpenTag) { @@ -290,6 +293,14 @@ void NGInlineLayoutAlgorithm::CreateLine( box_states_->UpdateAfterReorder(&line_box_); } LayoutUnit inline_size = box_states_->ComputeInlinePositions(&line_box_); + if (LayoutUnit hang_width = line_info->HangWidth()) { + inline_size -= hang_width; + container_builder_.SetHangInlineSize(hang_width); + + if (IsRtl(line_info->BaseDirection())) { + line_box_.MoveInInlineDirection(-hang_width); + } + } // Truncate the line if 'text-overflow: ellipsis' is set. if (UNLIKELY(inline_size > line_info->AvailableWidth() && @@ -346,6 +357,7 @@ void NGInlineLayoutAlgorithm::CreateLine( // Even if we have something in-flow, it may just be empty items that // shouldn't trigger creation of a line. Exit now if that's the case. if (line_info->IsEmptyLine()) { + container_builder_.SetIsSelfCollapsing(); container_builder_.SetIsEmptyLineBox(); container_builder_.SetBaseDirection(line_info->BaseDirection()); container_builder_.AddChildren(line_box_); @@ -413,7 +425,7 @@ void NGInlineLayoutAlgorithm::PlaceControlItem(const NGInlineItem& item, // Place a generated content that does not exist in DOM nor in LayoutObject // tree. void NGInlineLayoutAlgorithm::PlaceGeneratedContent( - scoped_refptr<const NGPhysicalFragment> fragment, + scoped_refptr<const NGPhysicalTextFragment> fragment, UBiDiLevel bidi_level, NGInlineBoxState* box) { LayoutUnit inline_size = IsHorizontalWritingMode() ? fragment->Size().width @@ -466,7 +478,7 @@ void NGInlineLayoutAlgorithm::PlaceLayoutResult(NGInlineItemResult* item_result, NGBoxFragment fragment(ConstraintSpace().GetWritingMode(), ConstraintSpace().Direction(), To<NGPhysicalBoxFragment>( - *item_result->layout_result->PhysicalFragment())); + item_result->layout_result->PhysicalFragment())); NGLineHeightMetrics metrics = fragment.BaselineMetrics( {NGBaselineAlgorithmType::kAtomicInline, baseline_type_}, ConstraintSpace()); @@ -475,7 +487,7 @@ void NGInlineLayoutAlgorithm::PlaceLayoutResult(NGInlineItemResult* item_result, LayoutUnit line_top = item_result->margins.line_over - metrics.ascent; line_box_.AddChild(std::move(item_result->layout_result), - NGLogicalOffset{inline_offset, line_top}, + LogicalOffset{inline_offset, line_top}, item_result->inline_size, item.BidiLevel()); } @@ -486,6 +498,8 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects( DCHECK(line_info.IsEmptyLine() || !line_box_metrics.IsEmpty()) << "Non-empty lines must have a valid set of linebox metrics."; + bool is_empty_inline = Node().IsEmptyInline(); + // All children within the linebox are positioned relative to the baseline, // then shifted later using NGLineBoxFragmentBuilder::MoveInBlockDirection. LayoutUnit baseline_adjustment = @@ -516,25 +530,34 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects( // To correctly determine which "line" block-level out-of-flow positioned // object is placed on, we need to keep track of if there is any inline-level // content preceeding it. - bool has_preceeding_inline_level_content = false; + bool has_preceding_inline_level_content = false; for (NGLineBoxFragmentBuilder::Child& child : line_box_) { - has_preceeding_inline_level_content |= child.HasInFlowFragment(); + has_preceding_inline_level_content |= child.HasInFlowFragment(); LayoutObject* box = child.out_of_flow_positioned_box; if (!box) continue; - NGLogicalOffset static_offset(LayoutUnit(), baseline_adjustment); + LogicalOffset static_offset(LayoutUnit(), baseline_adjustment); if (box->StyleRef().IsOriginalDisplayInlineType()) { // An inline-level OOF element positions itself within the line, at the // position it would have been if it was in-flow. static_offset.inline_offset = child.offset.inline_offset; + + // The static-position of inline-level OOF-positioned nodes depends on + // previous floats (if any). + // + // If we are an empty-inline we may not have the correct BFC block-offset + // yet. Due to this we need to mark this node as having adjoining + // objects, and perform a re-layout if our position shifts. + if (is_empty_inline) + container_builder_.AddAdjoiningFloatTypes(kAdjoiningInlineOutOfFlow); } else { // A block-level OOF element positions itself on the "next" line. However // only shifts down if there is inline-level content. static_offset.inline_offset = block_level_inline_offset; - if (has_preceeding_inline_level_content) + if (has_preceding_inline_level_content) static_offset.block_offset += line_height; } @@ -566,9 +589,8 @@ void NGInlineLayoutAlgorithm::PlaceFloatingObjects( bool is_empty_inline = Node().IsEmptyInline(); LayoutUnit bfc_block_offset = line_info.BfcOffset().block_offset; - if (is_empty_inline && ConstraintSpace().FloatsBfcBlockOffset()) { - bfc_block_offset = *ConstraintSpace().FloatsBfcBlockOffset(); - } + if (is_empty_inline && ConstraintSpace().ForcedBfcBlockOffset()) + bfc_block_offset = *ConstraintSpace().ForcedBfcBlockOffset(); LayoutUnit bfc_line_offset = container_builder_.BfcLineOffset(); @@ -587,7 +609,7 @@ void NGInlineLayoutAlgorithm::PlaceFloatingObjects( // Skip any children which aren't positioned floats. if (!child.layout_result || - !child.layout_result->PhysicalFragment()->IsFloating()) + !child.layout_result->PhysicalFragment().IsFloating()) continue; LayoutUnit block_offset = @@ -595,9 +617,8 @@ void NGInlineLayoutAlgorithm::PlaceFloatingObjects( // We need to manually account for the flipped-lines writing mode here :(. if (IsFlippedLinesWritingMode(ConstraintSpace().GetWritingMode())) { - NGFragment fragment( - ConstraintSpace().GetWritingMode(), - To<NGPhysicalBoxFragment>(*child.layout_result->PhysicalFragment())); + NGFragment fragment(ConstraintSpace().GetWritingMode(), + child.layout_result->PhysicalFragment()); block_offset = -fragment.BlockSize() - block_offset; } @@ -629,10 +650,7 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(LayoutUnit space, return false; // Justify the end of visible text, ignoring preserved trailing spaces. - unsigned end_offset; - LayoutUnit trailing_spaces_width = - line_info->ComputeTrailingSpaceWidth(&end_offset); - space += trailing_spaces_width; + unsigned end_offset = line_info->EndOffsetForJustify(); // If this line overflows, fallback to 'text-align: start'. if (space <= 0) @@ -700,8 +718,7 @@ LayoutUnit NGInlineLayoutAlgorithm::ApplyTextAlign(NGLineInfo* line_info) { LayoutUnit space = line_info->AvailableWidth() - line_info->WidthForAlignment(); - const ComputedStyle& line_style = line_info->LineStyle(); - ETextAlign text_align = line_style.GetTextAlign(line_info->IsLastLine()); + ETextAlign text_align = line_info->TextAlign(); if (text_align == ETextAlign::kJustify) { // If justification succeeds, no offset is needed. Expansions are set to // each |NGInlineItemResult| in |line_info|. @@ -781,16 +798,22 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { unsigned handled_leading_floats_index = PositionLeadingFloats(&initial_exclusion_space, &leading_floats); + // Only empty-inlines should have the "forced" BFC block-offset set. + DCHECK(is_empty_inline || !ConstraintSpace().ForcedBfcBlockOffset()); + // We query all the layout opportunities on the initial exclusion space up // front, as if the line breaker may add floats and change the opportunities. const LayoutOpportunityVector opportunities = initial_exclusion_space.AllLayoutOpportunities( - ConstraintSpace().BfcOffset(), + {ConstraintSpace().BfcOffset().line_offset, + ConstraintSpace().ForcedBfcBlockOffset().value_or( + ConstraintSpace().BfcOffset().block_offset)}, ConstraintSpace().AvailableSize().inline_size); NGExclusionSpace exclusion_space; const NGInlineBreakToken* break_token = BreakToken(); + bool is_line_created = false; LayoutUnit line_block_size; LayoutUnit block_delta; const auto* opportunities_it = opportunities.begin(); @@ -818,6 +841,7 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { // Reset any state that may have been modified in a previous pass. container_builder_.Reset(); exclusion_space = initial_exclusion_space; + is_line_created = false; NGLineLayoutOpportunity line_opportunity = opportunity.ComputeLineLayoutOpportunity(ConstraintSpace(), @@ -834,8 +858,8 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { // *and* the opportunity is smaller than the available inline-size, and the // container autowraps, continue to the next opportunity. if (line_info.HasOverflow() && - ConstraintSpace().AvailableSize().inline_size != - line_opportunity.AvailableFloatInlineSize() && + !line_opportunity.IsEqualToAvailableFloatInlineSize( + ConstraintSpace().AvailableSize().inline_size) && Node().Style().AutoWrap()) { // Shapes are *special*. We need to potentially increment the block-delta // by 1px each loop to properly test each potential position of the line. @@ -851,11 +875,15 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { line_block_size = LayoutUnit(); ++opportunities_it; } + // There must be at least one more opportunity, or we fail to call + // |CreateLine()|. + DCHECK_NE(opportunities_it, opportunities.end()); continue; } PrepareBoxStates(line_info, break_token); CreateLine(line_opportunity, &line_info, &exclusion_space); + is_line_created = true; // We now can check the block-size of the fragment, and it fits within the // opportunity. @@ -894,10 +922,6 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { continue; } - if (opportunity.rect.BlockStartOffset() > - ConstraintSpace().BfcOffset().block_offset) - container_builder_.SetIsPushedByFloats(); - // Success! container_builder_.SetBreakToken(line_breaker.CreateBreakToken(line_info)); @@ -909,10 +933,20 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { // TODO(ikilpatrick): Move this into ng_block_layout_algorithm. container_builder_.SetBlockSize( ComputeContentSize(line_info, exclusion_space, line_height)); + + // As we aren't an empty inline we should have correctly placed all + // our adjoining floats, and shouldn't propagate this information + // to siblings. + container_builder_.ResetAdjoiningFloatTypes(); + + if (opportunity.rect.BlockStartOffset() > + ConstraintSpace().BfcOffset().block_offset) + container_builder_.SetIsPushedByFloats(); } break; } + CHECK(is_line_created); container_builder_.SetExclusionSpace(std::move(exclusion_space)); container_builder_.MoveOutOfFlowDescendantCandidatesToDescendants(); return container_builder_.ToLineBoxFragment(); @@ -948,13 +982,13 @@ unsigned NGInlineLayoutAlgorithm::PositionLeadingFloats( ? kFloatTypeLeft : kFloatTypeRight); - // If we are an empty inline, and don't have the special floats BFC + // If we are an empty inline, and don't have the special forced BFC // block-offset yet, there is no way to position any floats. - if (is_empty_inline && !ConstraintSpace().FloatsBfcBlockOffset()) + if (is_empty_inline && !ConstraintSpace().ForcedBfcBlockOffset()) continue; LayoutUnit origin_bfc_block_offset = - is_empty_inline ? *ConstraintSpace().FloatsBfcBlockOffset() + is_empty_inline ? *ConstraintSpace().ForcedBfcBlockOffset() : ConstraintSpace().BfcOffset().block_offset; NGPositionedFloat positioned_float = PositionFloat( diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h index e12d473e761..d93ab75c097 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h @@ -80,7 +80,7 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final const NGLineInfo&, NGInlineItemResult*, NGInlineBoxState*); - void PlaceGeneratedContent(scoped_refptr<const NGPhysicalFragment>, + void PlaceGeneratedContent(scoped_refptr<const NGPhysicalTextFragment>, UBiDiLevel, NGInlineBoxState*); NGInlineBoxState* PlaceAtomicInline(const NGInlineItem&, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc index f1d65e70440..1c01a0a125e 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc @@ -41,7 +41,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, BreakToken) { auto* block_flow = To<LayoutBlockFlow>(GetLayoutObjectByElementId("container")); NGInlineNode inline_node(block_flow); - NGLogicalSize size(LayoutUnit(50), LayoutUnit(20)); + LogicalSize size(LayoutUnit(50), LayoutUnit(20)); NGConstraintSpace constraint_space = NGConstraintSpaceBuilder( @@ -53,23 +53,20 @@ TEST_F(NGInlineLayoutAlgorithmTest, BreakToken) { NGInlineChildLayoutContext context; scoped_refptr<const NGLayoutResult> layout_result = inline_node.Layout(constraint_space, nullptr, &context); - auto* line1 = - To<NGPhysicalLineBoxFragment>(layout_result->PhysicalFragment()); - EXPECT_FALSE(line1->BreakToken()->IsFinished()); + const auto& line1 = layout_result->PhysicalFragment(); + EXPECT_FALSE(line1.BreakToken()->IsFinished()); // Perform 2nd layout with the break token from the 1st line. scoped_refptr<const NGLayoutResult> layout_result2 = - inline_node.Layout(constraint_space, line1->BreakToken(), &context); - auto* line2 = - To<NGPhysicalLineBoxFragment>(layout_result2->PhysicalFragment()); - EXPECT_FALSE(line2->BreakToken()->IsFinished()); + inline_node.Layout(constraint_space, line1.BreakToken(), &context); + const auto& line2 = layout_result2->PhysicalFragment(); + EXPECT_FALSE(line2.BreakToken()->IsFinished()); // Perform 3rd layout with the break token from the 2nd line. scoped_refptr<const NGLayoutResult> layout_result3 = - inline_node.Layout(constraint_space, line2->BreakToken(), &context); - auto* line3 = - To<NGPhysicalLineBoxFragment>(layout_result3->PhysicalFragment()); - EXPECT_TRUE(line3->BreakToken()->IsFinished()); + inline_node.Layout(constraint_space, line2.BreakToken(), &context); + const auto& line3 = layout_result3->PhysicalFragment(); + EXPECT_TRUE(line3.BreakToken()->IsFinished()); } TEST_F(NGInlineLayoutAlgorithmTest, GenerateHyphen) { @@ -168,10 +165,10 @@ TEST_F(NGInlineLayoutAlgorithmTest, To<NGPhysicalLineBoxFragment>(*container->Children()[0]); EXPECT_EQ(1u, linebox.Children().size()); - EXPECT_EQ(NGPhysicalSize(), linebox.Size()); + EXPECT_EQ(PhysicalSize(), linebox.Size()); const auto& oof_container = To<NGPhysicalBoxFragment>(*linebox.Children()[0]); - EXPECT_EQ(NGPhysicalSize(), oof_container.Size()); + EXPECT_EQ(PhysicalSize(), oof_container.Size()); } // This test ensures that if an inline box generates (or does not generate) box @@ -234,13 +231,12 @@ TEST_F(NGInlineLayoutAlgorithmTest, ContainerBorderPadding) { NGConstraintSpace::CreateFromLayoutObject(*block_flow); scoped_refptr<const NGLayoutResult> layout_result = block_node.Layout(space); - auto* block_box = - To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment()); EXPECT_TRUE(layout_result->BfcBlockOffset().has_value()); EXPECT_EQ(0, *layout_result->BfcBlockOffset()); EXPECT_EQ(0, layout_result->BfcLineOffset()); - NGPhysicalOffset line_offset = block_box->Children()[0].Offset(); + PhysicalOffset line_offset = + layout_result->PhysicalFragment().Children()[0].Offset(); EXPECT_EQ(5, line_offset.left); EXPECT_EQ(10, line_offset.top); } @@ -270,9 +266,9 @@ TEST_F(NGInlineLayoutAlgorithmTest, MAYBE_VerticalAlignBottomReplaced) { scoped_refptr<const NGLayoutResult> layout_result = inline_node.Layout(space, nullptr, &context); - auto* line = To<NGPhysicalLineBoxFragment>(layout_result->PhysicalFragment()); - EXPECT_EQ(LayoutUnit(96), line->Size().height); - NGPhysicalOffset img_offset = line->Children()[0].Offset(); + const auto& line = layout_result->PhysicalFragment(); + EXPECT_EQ(LayoutUnit(96), line.Size().height); + PhysicalOffset img_offset = line.Children()[0].Offset(); EXPECT_EQ(LayoutUnit(0), img_offset.top); } @@ -316,7 +312,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, TextFloatsAroundFloatsBefore) { To<NGPhysicalBoxFragment>(html_fragment->Children()[0].get()); auto* container_fragment = To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); - Vector<NGPhysicalOffset> line_offsets; + Vector<PhysicalOffset> line_offsets; for (const auto& child : container_fragment->Children()) { if (!child->IsLineBox()) continue; @@ -368,7 +364,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, TextFloatsAroundInlineFloatThatFitsOnLine) { // Two lines. EXPECT_EQ(2u, block_box->Children().size()); - NGPhysicalOffset first_line_offset = block_box->Children()[1].Offset(); + PhysicalOffset first_line_offset = block_box->Children()[1].Offset(); // 30 == narrow-float's width. EXPECT_EQ(LayoutUnit(30), first_line_offset.left); @@ -500,7 +496,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, InkOverflow) { EXPECT_EQ(LayoutUnit(10), box_fragment.Size().height); - NGPhysicalOffsetRect ink_overflow = paint_fragment->InkOverflow(); + PhysicalRect ink_overflow = paint_fragment->InkOverflow(); EXPECT_EQ(LayoutUnit(-5), ink_overflow.offset.top); EXPECT_EQ(LayoutUnit(20), ink_overflow.size.height); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc index e2e32f01d1b..846eb8c7ef1 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc @@ -7,6 +7,7 @@ #include <algorithm> #include <memory> +#include "build/build_config.h" #include "third_party/blink/renderer/core/layout/layout_block_flow.h" #include "third_party/blink/renderer/core/layout/layout_inline.h" #include "third_party/blink/renderer/core/layout/layout_list_marker.h" @@ -15,6 +16,7 @@ #include "third_party/blink/renderer/core/layout/logical_values.h" #include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h" @@ -62,83 +64,28 @@ unsigned EstimateOffsetMappingItemsCount(const LayoutBlockFlow& block) { return EstimateInlineItemsCount(block) / 4; } -// This class marks appropriate line box fragments as dirty. -// -// |CollectInlinesInternal| calls this class when traversing the LayoutObject -// tree in pre-order DFS -class NGLineBoxMarker { - STACK_ALLOCATED(); - - public: - NGLineBoxMarker(NGPaintFragment* block_fragment) - : block_fragment_(block_fragment) { - DCHECK(block_fragment_); - } - - bool HandleText(LayoutText* layout_text) { - if (layout_text->SelfNeedsLayout()) - return Mark(); - return UpdateLastFragment(layout_text->FirstInlineFragment()); - } - - bool HandleInlineBox(LayoutInline* layout_inline) { - if (layout_inline->SelfNeedsLayout()) - return Mark(); - - // Do not keep fragments of LayoutInline unless it's a leaf, because - // the last fragment of LayoutInline is not the previous fragment of its - // descendants. - if (layout_inline->FirstChild()) - return false; - return UpdateLastFragment(layout_inline->FirstInlineFragment()); - } - - bool HandleAtomicInline(LayoutBox* layout_box) { - if (layout_box->NeedsLayout()) - return Mark(); - return UpdateLastFragment(layout_box->FirstInlineFragment()); - } - - private: - bool Mark() { - if (last_fragment_) { - // Changes in this LayoutObject may affect the line that contains its - // previous object. Mark the line box that contains the last fragment - // of the previous object. - last_fragment_->LastForSameLayoutObject()->MarkContainingLineBoxDirty(); - } else { - // If there were no fragments so far in this pre-order traversal, mark - // the first line box dirty. - DCHECK(block_fragment_); - if (NGPaintFragment* first_line = block_fragment_->FirstLineBox()) - first_line->MarkLineBoxDirty(); - } - return true; - } - - bool UpdateLastFragment(NGPaintFragment* fragment) { - if (fragment) - last_fragment_ = fragment; - return false; - } - - NGPaintFragment* block_fragment_; - NGPaintFragment* last_fragment_ = nullptr; -}; - // This class has the same interface as NGInlineItemsBuilder but does nothing // except tracking if floating or out-of-flow objects are added. // // |MarkLineBoxesDirty| uses this class to traverse tree without buildling // |NGInlineItem|. class ItemsBuilderForMarkLineBoxesDirty { + STACK_ALLOCATED(); + public: - void AppendText(const String&, LayoutText*) {} - bool AppendTextReusing(const String&, LayoutText*) { return false; } + ItemsBuilderForMarkLineBoxesDirty(NGDirtyLines* dirty_lines) + : dirty_lines_(dirty_lines) {} + void AppendText(LayoutText* layout_text, const NGInlineItemsData*) { + if (dirty_lines_ && dirty_lines_->HandleText(layout_text)) + dirty_lines_ = nullptr; + } void AppendOpaque(NGInlineItem::NGInlineItemType, LayoutObject*) {} - void AppendBreakOpportunity(LayoutObject*) {} - void AppendAtomicInline(LayoutObject*) {} + void AppendAtomicInline(LayoutObject* layout_object) { + if (dirty_lines_ && + dirty_lines_->HandleAtomicInline(ToLayoutBox(layout_object))) + dirty_lines_ = nullptr; + } void AppendFloating(LayoutObject*) { has_floating_or_out_of_flow_positioned_ = true; } @@ -148,7 +95,10 @@ class ItemsBuilderForMarkLineBoxesDirty { void SetIsSymbolMarker(bool) {} void EnterBlock(const ComputedStyle*) {} void ExitBlock() {} - void EnterInline(LayoutObject*) {} + void EnterInline(LayoutInline* layout_inline) { + if (dirty_lines_ && dirty_lines_->HandleInlineBox(layout_inline)) + dirty_lines_ = nullptr; + } void ExitInline(LayoutObject*) {} bool ShouldAbort() const { @@ -164,12 +114,12 @@ class ItemsBuilderForMarkLineBoxesDirty { } void ClearInlineFragment(LayoutObject* object) { - NGInlineNode::ClearInlineFragment(object); + DCHECK(object->IsInLayoutNGInlineFormattingContext()); } void ClearNeedsLayout(LayoutObject* object) { object->ClearNeedsLayout(); - object->ClearNeedsCollectInlines(); + DCHECK(!object->NeedsCollectInlines()); ClearInlineFragment(object); } @@ -178,6 +128,7 @@ class ItemsBuilderForMarkLineBoxesDirty { } private: + NGDirtyLines* dirty_lines_; bool has_floating_or_out_of_flow_positioned_ = false; }; @@ -194,8 +145,7 @@ class ItemsBuilderForMarkLineBoxesDirty { template <typename ItemsBuilder> void CollectInlinesInternal(LayoutBlockFlow* block, ItemsBuilder* builder, - String* previous_text, - NGLineBoxMarker* marker) { + const NGInlineNodeData* previous_data) { builder->EnterBlock(block->Style()); LayoutObject* node = GetLayoutObjectForFirstChildNode(block); @@ -203,28 +153,11 @@ void CollectInlinesInternal(LayoutBlockFlow* block, LayoutNGListItem::FindSymbolMarkerLayoutText(block); while (node) { if (LayoutText* layout_text = ToLayoutTextOrNull(node)) { - // If the LayoutText element hasn't changed, reuse the existing items. - - // if the last ended with space and this starts with space, do not allow - // reuse. builder->MightCollapseWithPreceding(*previous_text) - bool item_reused = false; - if (previous_text && layout_text->HasValidInlineItems()) - item_reused = builder->AppendTextReusing(*previous_text, layout_text); - - // If not create a new item as needed. - if (!item_reused) { - if (UNLIKELY(layout_text->IsWordBreak())) - builder->AppendBreakOpportunity(layout_text); - else - builder->AppendText(layout_text->GetText(), layout_text); - } + builder->AppendText(layout_text, previous_data); if (symbol == layout_text) builder->SetIsSymbolMarker(true); - if (marker && marker->HandleText(layout_text)) - marker = nullptr; - builder->ClearNeedsLayout(layout_text); } else if (node->IsFloating()) { @@ -252,9 +185,6 @@ void CollectInlinesInternal(LayoutBlockFlow* block, // signal the presence of a non-text object to the unicode bidi // algorithm. builder->AppendAtomicInline(node); - - if (marker && marker->HandleAtomicInline(ToLayoutBox(node))) - marker = nullptr; } builder->ClearInlineFragment(node); @@ -268,9 +198,6 @@ void CollectInlinesInternal(LayoutBlockFlow* block, builder->EnterInline(layout_inline); - if (marker && marker->HandleInlineBox(layout_inline)) - marker = nullptr; - // Traverse to children if they exist. if (LayoutObject* child = layout_inline->FirstChild()) { node = child; @@ -302,7 +229,65 @@ void CollectInlinesInternal(LayoutBlockFlow* block, builder->ExitBlock(); } -static bool NeedsShaping(const NGInlineItem& item) { +// Returns whether this text should break shaping. Even within a box, text runs +// that have different shaping properties need to break shaping. +inline bool ShouldBreakShapingBeforeText(const NGInlineItem& item, + const NGInlineItem& start_item, + const ComputedStyle& start_style, + const Font& start_font, + TextDirection start_direction) { + DCHECK_EQ(item.Type(), NGInlineItem::kText); + DCHECK(item.Style()); + const ComputedStyle& style = *item.Style(); + if (&style != &start_style) { + const Font& font = style.GetFont(); + if (&font != &start_font && font != start_font) + return true; + } + + // The resolved direction and run segment properties must match to shape + // across for HarfBuzzShaper. + return item.Direction() != start_direction || + !item.EqualsRunSegment(start_item); +} + +// Returns whether the start of this box should break shaping. +inline bool ShouldBreakShapingBeforeBox(const NGInlineItem& item, + const Font& start_font) { + DCHECK_EQ(item.Type(), NGInlineItem::kOpenTag); + DCHECK(item.Style()); + const ComputedStyle& style = *item.Style(); + + // These properties values must break shaping. + // https://drafts.csswg.org/css-text-3/#boundary-shaping + if ((style.MayHavePadding() && !style.PaddingStart().IsZero()) || + (style.MayHaveMargin() && !style.MarginStart().IsZero()) || + style.BorderStartWidth() || + style.VerticalAlign() != EVerticalAlign::kBaseline) + return true; + + return false; +} + +// Returns whether the end of this box should break shaping. +inline bool ShouldBreakShapingAfterBox(const NGInlineItem& item, + const Font& start_font) { + DCHECK_EQ(item.Type(), NGInlineItem::kCloseTag); + DCHECK(item.Style()); + const ComputedStyle& style = *item.Style(); + + // These properties values must break shaping. + // https://drafts.csswg.org/css-text-3/#boundary-shaping + if ((style.MayHavePadding() && !style.PaddingEnd().IsZero()) || + (style.MayHaveMargin() && !style.MarginEnd().IsZero()) || + style.BorderEndWidth() || + style.VerticalAlign() != EVerticalAlign::kBaseline) + return true; + + return false; +} + +inline bool NeedsShaping(const NGInlineItem& item) { return item.Type() == NGInlineItem::kText && !item.TextShapeResult(); } @@ -363,17 +348,31 @@ void NGInlineNode::PrepareLayoutIfNeeded() { block_flow->ResetNGInlineNodeData(); } + if (RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) { + if (const NGPaintFragment* fragment = block_flow->PaintFragment()) { + NGDirtyLines dirty_lines(fragment); + PrepareLayout(std::move(previous_data), &dirty_lines); + return; + } + } + PrepareLayout(std::move(previous_data), /* dirty_lines */ nullptr); +} + +void NGInlineNode::PrepareLayout( + std::unique_ptr<NGInlineNodeData> previous_data, + NGDirtyLines* dirty_lines) { // Scan list of siblings collecting all in-flow non-atomic inlines. A single // NGInlineNode represent a collection of adjacent non-atomic inlines. NGInlineNodeData* data = MutableData(); DCHECK(data); - CollectInlines(data, previous_data.get()); + CollectInlines(data, previous_data.get(), dirty_lines); SegmentText(data); ShapeText(data, previous_data.get()); ShapeTextForFirstLineIfNeeded(data); AssociateItemsWithInlines(data); DCHECK_EQ(data, MutableData()); + LayoutBlockFlow* block_flow = GetLayoutBlockFlow(); block_flow->ClearNeedsCollectInlines(); #if DCHECK_IS_ON() @@ -418,7 +417,7 @@ void NGInlineNode::ComputeOffsetMapping(LayoutBlockFlow* layout_block_flow, NGInlineItemsBuilderForOffsetMapping builder(&items); builder.GetOffsetMappingBuilder().ReserveCapacity( EstimateOffsetMappingItemsCount(*layout_block_flow)); - CollectInlinesInternal(layout_block_flow, &builder, nullptr, nullptr); + CollectInlinesInternal(layout_block_flow, &builder, nullptr); // For non-NG object, we need the text, and also the inline items to resolve // bidi levels. Otherwise |data| already has the text from the pre-layout @@ -443,6 +442,14 @@ const NGOffsetMapping* NGInlineNode::GetOffsetMapping( LayoutBlockFlow* layout_block_flow) { DCHECK(!layout_block_flow->GetDocument().NeedsLayoutTreeUpdate()); + if (UNLIKELY(layout_block_flow->NeedsLayout())) { + // TODO(kojii): This shouldn't happen, but is not easy to fix all cases. + // Return nullptr so that callers can chose to fail gracefully, or + // null-deref. crbug.com/946004 + NOTREACHED(); + return nullptr; + } + // If |layout_block_flow| is LayoutNG, compute from |NGInlineNode|. if (layout_block_flow->IsLayoutNGMixin()) { NGInlineNode node(layout_block_flow); @@ -466,23 +473,16 @@ const NGOffsetMapping* NGInlineNode::GetOffsetMapping( // parent LayoutInline where possible, and joining all text content in a single // string to allow bidi resolution and shaping of the entire block. void NGInlineNode::CollectInlines(NGInlineNodeData* data, - NGInlineNodeData* previous_data) { + NGInlineNodeData* previous_data, + NGDirtyLines* dirty_lines) { DCHECK(data->text_content.IsNull()); DCHECK(data->items.IsEmpty()); LayoutBlockFlow* block = GetLayoutBlockFlow(); block->WillCollectInlines(); - // If we have PaintFragment, mark line boxes dirty from |NeedsLayout| flag. - base::Optional<NGLineBoxMarker> marker; - if (NGPaintFragment* block_fragment = block->PaintFragment()) - marker.emplace(block_fragment); - - String* previous_text = - previous_data ? &previous_data->text_content : nullptr; data->items.ReserveCapacity(EstimateInlineItemsCount(*block)); - NGInlineItemsBuilder builder(&data->items); - CollectInlinesInternal(block, &builder, previous_text, - marker.has_value() ? &*marker : nullptr); + NGInlineItemsBuilder builder(&data->items, dirty_lines); + CollectInlinesInternal(block, &builder, previous_data); data->text_content = builder.ToString(); // Set |is_bidi_enabled_| for all UTF-16 strings for now, because at this @@ -678,22 +678,27 @@ void NGInlineNode::ShapeText(NGInlineItemsData* data, if (item.Type() == NGInlineItem::kText) { if (!item.Length()) continue; - // Shape adjacent items together if the font and direction matches to - // allow ligatures and kerning to apply. - // Also run segment properties must match because NGInlineItem gives - // pre-segmented range to HarfBuzzShaper. - // TODO(kojii): Figure out the exact conditions under which this - // behavior is desirable. - if (font != item.Style()->GetFont() || direction != item.Direction() || - !item.EqualsRunSegment(start_item)) + if (ShouldBreakShapingBeforeText(item, start_item, start_style, font, + direction)) { + break; + } + // Break shaping at ZWNJ so that it prevents kerning. ZWNJ is always at + // the beginning of an item for this purpose; see NGInlineItemsBuilder. + if (text_content[item.StartOffset()] == kZeroWidthNonJoinerCharacter) break; end_offset = item.EndOffset(); num_text_items++; - } else if (item.Type() == NGInlineItem::kOpenTag || - item.Type() == NGInlineItem::kCloseTag) { - // These items are opaque to shaping. - // Opaque items cannot have text, such as Object Replacement Characters, - // since such characters can affect shaping. + } else if (item.Type() == NGInlineItem::kOpenTag) { + if (ShouldBreakShapingBeforeBox(item, font)) { + break; + } + // Should not have any characters to be opaque to shaping. + DCHECK_EQ(0u, item.Length()); + } else if (item.Type() == NGInlineItem::kCloseTag) { + if (ShouldBreakShapingAfterBox(item, font)) { + break; + } + // Should not have any characters to be opaque to shaping. DCHECK_EQ(0u, item.Length()); } else { break; @@ -841,22 +846,6 @@ void NGInlineNode::ShapeTextForFirstLineIfNeeded(NGInlineNodeData* data) { item.SetStyleVariant(NGStyleVariant::kFirstLine); } - // Check if we have a first-line anonymous inline box. It is the first - // open-tag if we have. - for (auto& item : first_line_items->items) { - if (item.Type() == NGInlineItem::kOpenTag) { - if (item.layout_object_->IsAnonymous() && - item.layout_object_->IsLayoutInline() && - item.layout_object_->Parent() == GetLayoutBox() && - ToLayoutInline(item.layout_object_)->IsFirstLineAnonymous()) { - item.SetShouldCreateBoxFragment(); - } - break; - } - if (item.Type() != NGInlineItem::kBidiControl) - break; - } - // Re-shape if the font is different. if (needs_reshape || FirstLineNeedsReshape(*first_line_style, *block_style)) ShapeText(first_line_items.get()); @@ -876,12 +865,18 @@ void NGInlineNode::AssociateItemsWithInlines(NGInlineNodeData* data) { // Items split from a LayoutObject should be consecutive. DCHECK(associated_objects.insert(object).is_new_entry); #endif + layout_text->ClearHasBidiControlInlineItems(); + bool has_bidi_control = false; NGInlineItem* begin = item; for (++item; item != items.end(); ++item) { if (item->GetLayoutObject() != object) break; + if (item->Type() == NGInlineItem::kBidiControl) + has_bidi_control = true; } layout_text->SetInlineItems(begin, item); + if (has_bidi_control) + layout_text->SetHasBidiControlInlineItems(); continue; } ++item; @@ -891,7 +886,7 @@ void NGInlineNode::AssociateItemsWithInlines(NGInlineNodeData* data) { void NGInlineNode::ClearAssociatedFragments( const NGPhysicalFragment& fragment, const NGBlockBreakToken* block_break_token) { - auto* block_flow = To<LayoutBlockFlow>(fragment.GetLayoutObject()); + auto* block_flow = To<LayoutBlockFlow>(fragment.GetMutableLayoutObject()); if (!block_flow->ChildrenInline()) return; NGInlineNode node = NGInlineNode(block_flow); @@ -949,11 +944,28 @@ scoped_refptr<const NGLayoutResult> NGInlineNode::Layout( const auto* inline_break_token = To<NGInlineBreakToken>(break_token); NGInlineLayoutAlgorithm algorithm(*this, constraint_space, inline_break_token, context); - return algorithm.Layout(); + auto layout_result = algorithm.Layout(); + +#if defined(OS_ANDROID) + // Cached position data is crucial for line breaking performance and is + // preserved across layouts to speed up subsequent layout passes due to + // reflow, page zoom, window resize, etc. On Android though reflows are less + // common, page zoom isn't used (instead uses pinch-zoom), and the window + // typically can't be resized (apart from rotation). To reduce memory usage + // discard the cached position data after layout. + NGInlineNodeData* data = MutableData(); + for (auto& item : data->items) { + if (item.shape_result_) + item.shape_result_->DiscardPositionData(); + } +#endif // defined(OS_ANDROID) + + return layout_result; } const NGPaintFragment* NGInlineNode::ReusableLineBoxContainer( const NGConstraintSpace& constraint_space) { + DCHECK(RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()); // |SelfNeedsLayout()| is the most common reason that we check it earlier. LayoutBlockFlow* block_flow = GetLayoutBlockFlow(); DCHECK(!block_flow->SelfNeedsLayout()); @@ -987,7 +999,8 @@ const NGPaintFragment* NGInlineNode::ReusableLineBoxContainer( return nullptr; // Propagating OOF needs re-layout. - if (!cached_layout_result->OutOfFlowPositionedDescendants().IsEmpty()) + if (cached_layout_result->PhysicalFragment() + .HasOutOfFlowPositionedDescendants()) return nullptr; // Cached fragments are not for intermediate layout. @@ -1002,11 +1015,9 @@ const NGPaintFragment* NGInlineNode::ReusableLineBoxContainer( if (!paint_fragment) return nullptr; - if (!MarkLineBoxesDirty(block_flow)) + if (!MarkLineBoxesDirty(block_flow, paint_fragment)) return nullptr; - PrepareLayoutIfNeeded(); - if (Data().changes_may_affect_earlier_lines_) return nullptr; @@ -1018,10 +1029,19 @@ const NGPaintFragment* NGInlineNode::ReusableLineBoxContainer( // Removals of LayoutObject already marks relevant line boxes dirty by calling // |DirtyLinesFromChangedChild()|, but insertions and style changes are not // marked yet. -bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow) { - NGLineBoxMarker marker(block_flow->PaintFragment()); - ItemsBuilderForMarkLineBoxesDirty builder; - CollectInlinesInternal(block_flow, &builder, nullptr, &marker); +bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow, + const NGPaintFragment* paint_fragment) { + DCHECK(RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()); + NGDirtyLines dirty_lines(paint_fragment); + if (block_flow->NeedsCollectInlines()) { + std::unique_ptr<NGInlineNodeData> previous_data; + previous_data.reset(block_flow->TakeNGInlineNodeData()); + block_flow->ResetNGInlineNodeData(); + PrepareLayout(std::move(previous_data), &dirty_lines); + return true; + } + ItemsBuilderForMarkLineBoxesDirty builder(&dirty_lines); + CollectInlinesInternal(block_flow, &builder, nullptr); return !builder.ShouldAbort(); } @@ -1042,7 +1062,7 @@ static LayoutUnit ComputeContentSize( /* out_writing_mode */ writing_mode, /* is_new_fc */ false) .SetTextDirection(style.Direction()) - .SetAvailableSize({available_inline_size, NGSizeIndefinite}) + .SetAvailableSize({available_inline_size, kIndefiniteSize}) .SetPercentageResolutionSize({LayoutUnit(), LayoutUnit()}) .SetReplacedPercentageResolutionSize({LayoutUnit(), LayoutUnit()}) .SetIsIntermediateLayout(true) diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h index 94052b21d64..a3bcb07a50b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h @@ -9,6 +9,7 @@ #include "third_party/blink/renderer/core/layout/layout_block_flow.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h" +#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" #include "third_party/blink/renderer/platform/wtf/casting.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" @@ -16,10 +17,11 @@ namespace blink { class NGBlockBreakToken; class NGConstraintSpace; +class NGDirtyLines; class NGInlineChildLayoutContext; +class NGInlineNodeLegacy; class NGLayoutResult; class NGOffsetMapping; -class NGInlineNodeLegacy; struct MinMaxSize; struct NGInlineItemsData; @@ -59,8 +61,11 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { // Instruct to re-compute |PrepareLayout| on the next layout. void InvalidatePrepareLayoutForTest() { - GetLayoutBlockFlow()->ResetNGInlineNodeData(); + LayoutBlockFlow* block_flow = GetLayoutBlockFlow(); + block_flow->ResetNGInlineNodeData(); DCHECK(!IsPrepareLayoutFinished()); + // There shouldn't be paint fragment if NGInlineNodeData does not exist. + block_flow->SetPaintFragment(nullptr, nullptr); } const NGInlineItemsData& ItemsData(bool is_first_line) const { @@ -100,21 +105,18 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { String ToString() const; - // A helper function for NGInlineItemsBuilder. - static void ClearInlineFragment(LayoutObject* object) { - object->SetIsInLayoutNGInlineFormattingContext(true); - object->SetFirstInlineFragment(nullptr); - } - protected: bool IsPrepareLayoutFinished() const; // Prepare inline and text content for layout. Must be called before // calling the Layout method. void PrepareLayoutIfNeeded(); + void PrepareLayout(std::unique_ptr<NGInlineNodeData> previous_data, + NGDirtyLines* dirty_lines); void CollectInlines(NGInlineNodeData*, - NGInlineNodeData* previous_data = nullptr); + NGInlineNodeData* previous_data = nullptr, + NGDirtyLines* dirty_lines = nullptr); void SegmentText(NGInlineNodeData*); void SegmentScriptRuns(NGInlineNodeData*); void SegmentFontOrientation(NGInlineNodeData*); @@ -124,7 +126,7 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { void ShapeTextForFirstLineIfNeeded(NGInlineNodeData*); void AssociateItemsWithInlines(NGInlineNodeData*); - bool MarkLineBoxesDirty(LayoutBlockFlow*); + bool MarkLineBoxesDirty(LayoutBlockFlow*, const NGPaintFragment*); NGInlineNodeData* MutableData() { return To<LayoutBlockFlow>(box_)->GetNGInlineNodeData(); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h index 64c19d1af7b..833d63944c7 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h @@ -19,6 +19,8 @@ struct CORE_EXPORT NGInlineNodeData : NGInlineItemsData { return static_cast<TextDirection>(base_direction_); } + bool IsEmptyInline() const { return is_empty_inline_; } + private: const NGInlineItemsData& ItemsData(bool is_first_line) const { return !is_first_line || !first_line_items_ @@ -29,6 +31,7 @@ struct CORE_EXPORT NGInlineNodeData : NGInlineItemsData { base_direction_ = static_cast<unsigned>(direction); } + friend class NGInlineItemsBuilderTest; friend class NGInlineNode; friend class NGInlineNodeLegacy; friend class NGInlineNodeForTest; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc index 2851a3b14ec..4ee6e041cba 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc @@ -21,6 +21,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" #include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/core/svg_names.h" namespace blink { @@ -77,7 +78,9 @@ class NGInlineNodeForTest : public NGInlineNode { void ShapeText() { NGInlineNode::ShapeText(MutableData()); } bool MarkLineBoxesDirty() { - return NGInlineNode::MarkLineBoxesDirty(GetLayoutBlockFlow()); + LayoutBlockFlow* block_flow = GetLayoutBlockFlow(); + return NGInlineNode::MarkLineBoxesDirty(block_flow, + block_flow->PaintFragment()); } }; @@ -132,9 +135,9 @@ class NGInlineNodeTest : public NGLayoutTest { nullptr /* break_token */, &context) .Layout(); - const auto* line = + const auto& line = To<NGPhysicalLineBoxFragment>(result->PhysicalFragment()); - for (const auto& child : line->Children()) { + for (const auto& child : line.Children()) { fragments_out->push_back(To<NGPhysicalTextFragment>(child.get())); } } @@ -546,7 +549,7 @@ TEST_F(NGInlineNodeTest, NeedsCollectInlinesOnSetText) { Element* container = GetElementById("container"); Element* parent = GetElementById("parent"); - Text* text = ToText(parent->firstChild()); + auto* text = To<Text>(parent->firstChild()); EXPECT_FALSE(text->GetLayoutObject()->NeedsCollectInlines()); EXPECT_FALSE(parent->GetLayoutObject()->NeedsCollectInlines()); EXPECT_FALSE(container->GetLayoutObject()->NeedsCollectInlines()); @@ -579,28 +582,35 @@ struct StyleChangeData { kAll = kText | kParentAndAbove, }; unsigned needs_collect_inlines; + base::Optional<bool> is_line_dirty; } style_change_data[] = { // Changing color, text-decoration, etc. should not re-run // |CollectInlines()|. - {"#parent.after { color: red; }", StyleChangeData::kNone}, + {"#parent.after { color: red; }", StyleChangeData::kNone, false}, {"#parent.after { text-decoration-line: underline; }", - StyleChangeData::kNone}, + StyleChangeData::kNone, false}, // Changing fonts should re-run |CollectInlines()|. - {"#parent.after { font-size: 200%; }", StyleChangeData::kAll}, + {"#parent.after { font-size: 200%; }", StyleChangeData::kAll, true}, + // Changing from/to out-of-flow should re-rerun |CollectInlines()|. + {"#parent.after { position: absolute; }", StyleChangeData::kContainer, + true}, + {"#parent { position: absolute; }" + "#parent.after { position: initial; }", + StyleChangeData::kContainer, true}, // List markers are captured in |NGInlineItem|. {"#parent.after { display: list-item; }", StyleChangeData::kContainer}, {"#parent { display: list-item; list-style-type: none; }" "#parent.after { list-style-type: disc; }", - StyleChangeData::kTextAndParent}, + StyleChangeData::kParent}, {"#parent { display: list-item; }" "#container.after { list-style-type: none; }", - StyleChangeData::kTextAndParent}, + StyleChangeData::kParent}, // Changing properties related with bidi resolution should re-run // |CollectInlines()|. {"#parent.after { unicode-bidi: bidi-override; }", - StyleChangeData::kParentAndAbove}, + StyleChangeData::kParentAndAbove, true}, {"#container.after { unicode-bidi: bidi-override; }", - StyleChangeData::kContainer}, + StyleChangeData::kContainer, false}, }; std::ostream& operator<<(std::ostream& os, const StyleChangeData& data) { @@ -630,7 +640,7 @@ TEST_P(StyleChangeTest, NeedsCollectInlinesOnStyle) { Element* container = GetElementById("container"); Element* parent = GetElementById("parent"); - Text* text = ToText(parent->firstChild()); + auto* text = To<Text>(parent->firstChild()); EXPECT_FALSE(text->GetLayoutObject()->NeedsCollectInlines()); EXPECT_FALSE(parent->GetLayoutObject()->NeedsCollectInlines()); EXPECT_FALSE(container->GetLayoutObject()->NeedsCollectInlines()); @@ -654,6 +664,15 @@ TEST_P(StyleChangeTest, NeedsCollectInlinesOnStyle) { Element* next = GetElementById("next"); EXPECT_FALSE(previous->GetLayoutObject()->NeedsCollectInlines()); EXPECT_FALSE(next->GetLayoutObject()->NeedsCollectInlines()); + + if (data.is_line_dirty && + RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) { + layout_block_flow_ = ToLayoutNGBlockFlow(container->GetLayoutObject()); + auto lines = MarkLineBoxesDirty(); + EXPECT_EQ(*data.is_line_dirty, lines[0]->IsDirty()); + } + + ForceLayout(); // Ensure running layout does not crash. } using CreateNode = Node* (*)(Document&); @@ -964,47 +983,88 @@ TEST_F(NGInlineNodeTest, SpaceRestoredByInsertingWord) { } // Test marking line boxes when inserting a span before the first child. -TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnInsert) { +TEST_P(NodeInsertTest, MarkLineBoxesDirtyOnInsert) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( + <style> + .abspos { position: absolute; } + .float { float: left; } + </style> <div id=container style="font-size: 10px; width: 10ch"> 12345678 </div> )HTML"); - Element* span = GetDocument().CreateElementForBinding("span"); + Node* insert = (*GetParam())(GetDocument()); Element* container = GetElementById("container"); - container->insertBefore(span, container->firstChild()); + container->insertBefore(insert, container->firstChild()); auto lines = MarkLineBoxesDirty(); EXPECT_TRUE(lines[0]->IsDirty()); } // Test marking line boxes when appending a span. -TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnAppend) { +TEST_P(NodeInsertTest, MarkLineBoxesDirtyOnAppend) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( + <style> + .abspos { position: absolute; } + .float { float: left; } + </style> <div id=container style="font-size: 10px; width: 10ch"> 12345678 </div> )HTML"); - Element* span = GetDocument().CreateElementForBinding("span"); - layout_block_flow_->GetNode()->appendChild(span); + Node* insert = (*GetParam())(GetDocument()); + layout_block_flow_->GetNode()->appendChild(insert); auto lines = MarkLineBoxesDirty(); EXPECT_TRUE(lines[0]->IsDirty()); } // Test marking line boxes when appending a span on 2nd line. -TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnAppend2) { +TEST_P(NodeInsertTest, MarkLineBoxesDirtyOnAppend2) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( + <style> + .abspos { position: absolute; } + .float { float: left; } + </style> <div id=container style="font-size: 10px; width: 10ch"> 12345678 2234 </div> )HTML"); - Element* span = GetDocument().CreateElementForBinding("span"); - layout_block_flow_->GetNode()->appendChild(span); + Node* insert = (*GetParam())(GetDocument()); + layout_block_flow_->GetNode()->appendChild(insert); + + auto lines = MarkLineBoxesDirty(); + EXPECT_FALSE(lines[0]->IsDirty()); + EXPECT_TRUE(lines[1]->IsDirty()); +} + +// Test marking line boxes when appending a span on 2nd line. +TEST_P(NodeInsertTest, MarkLineBoxesDirtyOnAppendAfterBR) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; + SetupHtml("container", R"HTML( + <style> + .abspos { position: absolute; } + .float { float: left; } + </style> + <div id=container style="font-size: 10px; width: 10ch"> + <br> + <br> + </div> + )HTML"); + + Node* insert = (*GetParam())(GetDocument()); + layout_block_flow_->GetNode()->appendChild(insert); auto lines = MarkLineBoxesDirty(); EXPECT_FALSE(lines[0]->IsDirty()); @@ -1013,6 +1073,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnAppend2) { // Test marking line boxes when removing a span. TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemove) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( <div id=container style="font-size: 10px; width: 10ch"> 1234<span id=t>5678</span> @@ -1028,6 +1090,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemove) { // Test marking line boxes when removing a span. TEST_P(NodeParameterTest, MarkLineBoxesDirtyOnRemoveFirst) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", String(R"HTML( <div id=container style="font-size: 10px; width: 10ch">)HTML") + GetParam() + R"HTML(<span>after</span> @@ -1045,6 +1109,8 @@ TEST_P(NodeParameterTest, MarkLineBoxesDirtyOnRemoveFirst) { // Test marking line boxes when removing a span on 2nd line. TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemove2) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( <div id=container style="font-size: 10px; width: 10ch"> 12345678 @@ -1062,6 +1128,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemove2) { // Test marking line boxes when removing a text node on 2nd line. TEST_P(NodeParameterTest, MarkLineBoxesDirtyOnRemoveAfterBR) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", String(R"HTML( <div id=container style="font-size: 10px; width: 10ch"> line 1 @@ -1081,9 +1149,45 @@ TEST_P(NodeParameterTest, MarkLineBoxesDirtyOnRemoveAfterBR) { ForceLayout(); // Ensure running layout does not crash. } +TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnEndSpaceCollapsed) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; + SetupHtml("container", R"HTML( + <style> + div { + font-size: 10px; + width: 8ch; + } + #empty { + background: yellow; /* ensure fragment is created */ + } + #target { + display: inline-block; + } + </style> + <div id=container> + 1234567890 + 1234567890 + <span id=empty> </span> + <span id=target></span></div> + )HTML"); + + // Removing #target makes the spaces before it to be collapsed. + Element* target = GetElementById("target"); + target->remove(); + + auto lines = MarkLineBoxesDirty(); + EXPECT_FALSE(lines[0]->IsDirty()); + EXPECT_TRUE(lines[1]->IsDirty()); + + ForceLayout(); // Ensure running layout does not crash. +} + // Test marking line boxes when the first span has NeedsLayout. The span is // culled. TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutFirst) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( <div id=container style="font-size: 10px; width: 10ch"> <span id=t>1234</span>5678 @@ -1100,6 +1204,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutFirst) { // Test marking line boxes when the first span has NeedsLayout. The span has a // box fragment. TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutFirstWithBox) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( <div id=container style="font-size: 10px; width: 10ch"> <span id=t style="background: blue">1234</span>5678 @@ -1115,6 +1221,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutFirstWithBox) { // Test marking line boxes when a span has NeedsLayout. The span is culled. TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayout) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( <div id=container style="font-size: 10px; width: 10ch"> 12345678 @@ -1133,6 +1241,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayout) { // Test marking line boxes when a span has NeedsLayout. The span has a box // fragment. TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutWithBox) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( <div id=container style="font-size: 10px; width: 10ch"> 12345678 @@ -1152,6 +1262,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutWithBox) { // The parent span has a box fragment, and wraps, so that its fragment // is seen earlier in pre-order DFS. TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnChildOfWrappedBox) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( <div id=container style="font-size: 10px"> <span style="background: yellow"> @@ -1172,6 +1284,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnChildOfWrappedBox) { // Test marking line boxes when a span has NeedsLayout. The span has a box // fragment. TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyInInlineBlock) { + if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) + return; SetupHtml("container", R"HTML( <div id=container style="display: inline-block; font-size: 10px"> 12345678<br> @@ -1226,6 +1340,71 @@ TEST_F(NGInlineNodeTest, RemoveInlineNodeDataIfBlockObtainsBlockChild) { EXPECT_FALSE(layout_block_flow_->HasNGInlineNodeData()); } +// Test inline objects are initialized when |SplitFlow()| moves them. +TEST_F(NGInlineNodeTest, ClearFirstInlineFragmentOnSplitFlow) { + SetBodyInnerHTML(R"HTML( + <div> + <span id=outer_span> + <span id=inner_span>1234</span> + </span> + </div> + )HTML"); + + // Keep the text fragment to compare later. + Element* inner_span = GetElementById("inner_span"); + Node* text = inner_span->firstChild(); + scoped_refptr<NGPaintFragment> text_fragment_before_split = + text->GetLayoutObject()->FirstInlineFragment(); + EXPECT_NE(text_fragment_before_split.get(), nullptr); + + // Append <div> to <span>. causing SplitFlow(). + Element* outer_span = GetElementById("outer_span"); + Element* div = GetDocument().CreateRawElement(html_names::kDivTag); + outer_span->appendChild(div); + + // Update tree but do NOT update layout. At this point, there's no guarantee, + // but there are some clients (e.g., Schroll Anchor) who try to read + // associated fragments. + // + // NGPaintFragment is owned by LayoutNGBlockFlow. Because the original owner + // no longer has an inline formatting context, the NGPaintFragment subtree is + // destroyed, and should not be accessible. + GetDocument().UpdateStyleAndLayoutTree(); + scoped_refptr<NGPaintFragment> text_fragment_before_layout = + text->GetLayoutObject()->FirstInlineFragment(); + EXPECT_EQ(text_fragment_before_layout, nullptr); + + // Update layout. There should be a different instance of the text fragment. + UpdateAllLifecyclePhasesForTest(); + scoped_refptr<NGPaintFragment> text_fragment_after_layout = + text->GetLayoutObject()->FirstInlineFragment(); + EXPECT_NE(text_fragment_before_split, text_fragment_after_layout); + + // Check it is the one owned by the new root inline formatting context. + LayoutBlock* anonymous_block = + inner_span->GetLayoutObject()->ContainingBlock(); + EXPECT_TRUE(anonymous_block->IsAnonymous()); + const NGPaintFragment* block_fragment = anonymous_block->PaintFragment(); + const NGPaintFragment* line_box_fragment = block_fragment->FirstChild(); + EXPECT_EQ(line_box_fragment->FirstChild(), text_fragment_after_layout); +} + +TEST_F(NGInlineNodeTest, AddChildToSVGRoot) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + text + <svg id="svg"></svg> + </div> + )HTML"); + + Element* svg = GetElementById("svg"); + svg->appendChild(GetDocument().CreateRawElement(svg_names::kTextTag)); + GetDocument().UpdateStyleAndLayoutTree(); + + LayoutObject* container = GetLayoutObjectByElementId("container"); + EXPECT_FALSE(container->NeedsCollectInlines()); +} + // https://crbug.com/911220 TEST_F(NGInlineNodeTest, PreservedNewlineWithBidiAndRelayout) { SetupHtml("container", @@ -1241,6 +1420,44 @@ TEST_F(NGInlineNodeTest, PreservedNewlineWithBidiAndRelayout) { EXPECT_EQ(String(u"foo\u2066\u2069\n\u2066\u2069bar\nbaz"), GetText()); } +TEST_F(NGInlineNodeTest, PreservedNewlineWithRemovedBidiAndRelayout) { + SetupHtml("container", + "<pre id=container>foo<span dir=rtl>\nbar</span></pre>"); + EXPECT_EQ(String(u"foo\u2067\u2069\n\u2067bar\u2069"), GetText()); + + GetDocument().QuerySelector("span")->removeAttribute(html_names::kDirAttr); + UpdateAllLifecyclePhasesForTest(); + + // The bidi control characters around '\n' should not preserve + EXPECT_EQ("foo\nbar", GetText()); +} + +TEST_F(NGInlineNodeTest, PreservedNewlineWithRemovedLtrDirAndRelayout) { + SetupHtml("container", + "<pre id=container>foo<span dir=ltr>\nbar</span></pre>"); + EXPECT_EQ(String(u"foo\u2066\u2069\n\u2066bar\u2069"), GetText()); + + GetDocument().QuerySelector("span")->removeAttribute(html_names::kDirAttr); + UpdateAllLifecyclePhasesForTest(); + + // The bidi control characters around '\n' should not preserve + EXPECT_EQ("foo\nbar", GetText()); +} + +// https://crbug.com/969089 +TEST_F(NGInlineNodeTest, InsertedWBRWithLineBreakInRelayout) { + SetupHtml("container", "<div id=container><span>foo</span>\nbar</div>"); + EXPECT_EQ("foo bar", GetText()); + + Element* div = GetElementById("container"); + Element* wbr = GetDocument().CreateElementForBinding("wbr"); + div->insertBefore(wbr, div->lastChild()); + UpdateAllLifecyclePhasesForTest(); + + // The '\n' should be collapsed by the inserted <wbr> + EXPECT_EQ(String(u"foo\u200Bbar"), GetText()); +} + #if SEGMENT_BREAK_TRANSFORMATION_FOR_EAST_ASIAN_WIDTH // https://crbug.com/879088 TEST_F(NGInlineNodeTest, RemoveSegmentBreakFromJapaneseInRelayout) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc index 8d66c3cc915..13d2b5d6e37 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc @@ -16,7 +16,6 @@ namespace blink { void NGLineBoxFragmentBuilder::Reset() { children_.Shrink(0); - offsets_.Shrink(0); child_break_tokens_.Shrink(0); inline_break_tokens_.Shrink(0); oof_positioned_candidates_.Shrink(0); @@ -28,7 +27,7 @@ void NGLineBoxFragmentBuilder::Reset() { has_last_resort_break_ = false; has_floating_descendants_ = false; has_orthogonal_flow_roots_ = false; - has_child_that_depends_on_percentage_block_size_ = false; + has_descendant_that_depends_on_percentage_block_size_ = false; has_block_fragmentation_ = false; may_have_descendant_above_block_start_ = false; } @@ -57,6 +56,12 @@ NGLineBoxFragmentBuilder::ChildList::LastInFlowChild() { } void NGLineBoxFragmentBuilder::ChildList::MoveInInlineDirection( + LayoutUnit delta) { + for (auto& child : children_) + child.offset.inline_offset += delta; +} + +void NGLineBoxFragmentBuilder::ChildList::MoveInInlineDirection( LayoutUnit delta, unsigned start, unsigned end) { @@ -78,13 +83,12 @@ void NGLineBoxFragmentBuilder::ChildList::MoveInBlockDirection(LayoutUnit delta, } void NGLineBoxFragmentBuilder::AddChildren(ChildList& children) { - offsets_.ReserveCapacity(children.size()); children_.ReserveCapacity(children.size()); for (auto& child : children) { if (child.layout_result) { DCHECK(!child.fragment); - AddChild(*child.layout_result, child.offset); + AddChild(child.layout_result->PhysicalFragment(), child.offset); child.layout_result.reset(); } else if (child.fragment) { AddChild(std::move(child.fragment), child.offset); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h index 573308b00b2..71c7396ed98 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h @@ -5,13 +5,15 @@ #ifndef NGLineBoxFragmentBuilder_h #define NGLineBoxFragmentBuilder_h -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h" #include "third_party/blink/renderer/platform/wtf/allocator.h" @@ -19,11 +21,10 @@ namespace blink { class ComputedStyle; class NGInlineBreakToken; -class NGPhysicalFragment; class CORE_EXPORT NGLineBoxFragmentBuilder final : public NGContainerFragmentBuilder { - STACK_ALLOCATED(); + DISALLOW_NEW(); public: NGLineBoxFragmentBuilder(NGInlineNode node, @@ -45,6 +46,14 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final return metrics_.LineHeight().ClampNegativeToZero(); } + void SetInlineSize(LayoutUnit inline_size) { + size_.inline_size = inline_size; + } + + void SetHangInlineSize(LayoutUnit hang_inline_size) { + hang_inline_size_ = hang_inline_size; + } + // Mark this line box is an "empty" line box. See NGLineBoxType. void SetIsEmptyLineBox(); @@ -67,12 +76,12 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final DISALLOW_NEW(); scoped_refptr<const NGLayoutResult> layout_result; - scoped_refptr<const NGPhysicalFragment> fragment; + scoped_refptr<const NGPhysicalTextFragment> fragment; LayoutObject* out_of_flow_positioned_box = nullptr; LayoutObject* unpositioned_float = nullptr; // The offset of the border box, initially in this child coordinate system. // |ComputeInlinePositions()| converts it to the offset within the line box. - NGLogicalOffset offset; + LogicalOffset offset; // The offset of a positioned float wrt. the root BFC. This should only be // set for positioned floats. NGBfcOffset bfc_offset; @@ -90,29 +99,29 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final Child() = default; // Create a placeholder. A placeholder does not have a fragment nor a bidi // level. - Child(NGLogicalOffset offset) : offset(offset) {} + Child(LogicalOffset offset) : offset(offset) {} // Crete a bidi control. A bidi control does not have a fragment, but has // bidi level and affects bidi reordering. Child(UBiDiLevel bidi_level) : bidi_level(bidi_level) {} // Create an in-flow |NGLayoutResult|. Child(scoped_refptr<const NGLayoutResult> layout_result, - NGLogicalOffset offset, + LogicalOffset offset, LayoutUnit inline_size, UBiDiLevel bidi_level) : layout_result(std::move(layout_result)), offset(offset), inline_size(inline_size), bidi_level(bidi_level) {} - // Create an in-flow |NGPhysicalFragment|. - Child(scoped_refptr<const NGPhysicalFragment> fragment, - NGLogicalOffset offset, + // Create an in-flow |NGPhysicalTextFragment|. + Child(scoped_refptr<const NGPhysicalTextFragment> fragment, + LogicalOffset offset, LayoutUnit inline_size, UBiDiLevel bidi_level) : fragment(std::move(fragment)), offset(offset), inline_size(inline_size), bidi_level(bidi_level) {} - Child(scoped_refptr<const NGPhysicalFragment> fragment, + Child(scoped_refptr<const NGPhysicalTextFragment> fragment, LayoutUnit block_offset, LayoutUnit inline_size, UBiDiLevel bidi_level) @@ -142,7 +151,7 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final if (fragment) return true; - if (layout_result && !layout_result->PhysicalFragment()->IsFloating()) + if (layout_result && !layout_result->PhysicalFragment().IsFloating()) return true; return false; @@ -154,7 +163,9 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final bool HasBidiLevel() const { return bidi_level != 0xff; } bool IsPlaceholder() const { return !HasFragment() && !HasBidiLevel(); } const NGPhysicalFragment* PhysicalFragment() const { - return layout_result ? layout_result->PhysicalFragment() : fragment.get(); + if (layout_result) + return &layout_result->PhysicalFragment(); + return fragment.get(); } }; @@ -162,7 +173,7 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final // Unlike the fragment builder, chlidren are mutable. // Callers can add to the fragment builder in a batch once finalized. class ChildList { - STACK_ALLOCATED(); + DISALLOW_NEW(); public: ChildList() = default; @@ -204,13 +215,14 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final } void InsertChild(unsigned index, scoped_refptr<const NGLayoutResult> layout_result, - const NGLogicalOffset& offset, + const LogicalOffset& offset, LayoutUnit inline_size, UBiDiLevel bidi_level) { children_.insert(index, Child{std::move(layout_result), offset, inline_size, bidi_level}); } + void MoveInInlineDirection(LayoutUnit); void MoveInInlineDirection(LayoutUnit, unsigned start, unsigned end); void MoveInBlockDirection(LayoutUnit); void MoveInBlockDirection(LayoutUnit, unsigned start, unsigned end); @@ -227,6 +239,7 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final private: NGLineHeightMetrics metrics_; + LayoutUnit hang_inline_size_; NGPhysicalLineBoxFragment::NGLineBoxType line_box_type_; TextDirection base_direction_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc index d36e73f5419..8f60a66ffb1 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc @@ -157,6 +157,14 @@ inline void ClearNeedsLayout(const NGInlineItem& item) { } // namespace +LayoutUnit NGLineBreaker::ComputeAvailableWidth() const { + LayoutUnit available_width = line_opportunity_.AvailableInlineSize(); + // Available width must be smaller than |LayoutUnit::Max()| so that the + // position can be larger. + available_width = std::min(available_width, LayoutUnit::NearlyMax()); + return available_width; +} + NGLineBreaker::NGLineBreaker(NGInlineNode node, NGLineBreakerMode mode, const NGConstraintSpace& space, @@ -184,6 +192,7 @@ NGLineBreaker::NGLineBreaker(NGInlineNode node, leading_floats_(leading_floats), handled_leading_floats_index_(handled_leading_floats_index), base_direction_(node_.BaseDirection()) { + available_width_ = ComputeAvailableWidth(); break_iterator_.SetBreakSpace(BreakSpaceType::kBeforeSpaceRun); if (break_token) { @@ -235,20 +244,23 @@ void NGLineBreaker::SetMaxSizeCache(MaxSizeCache* max_size_cache) { max_size_cache_ = max_size_cache; } -void NGLineBreaker::SetLineEndFragment( +LayoutUnit NGLineBreaker::SetLineEndFragment( scoped_refptr<const NGPhysicalTextFragment> fragment, NGLineInfo* line_info) { + LayoutUnit inline_size; bool is_horizontal = IsHorizontalWritingMode(constraint_space_.GetWritingMode()); if (line_info->LineEndFragment()) { - const NGPhysicalSize& size = line_info->LineEndFragment()->Size(); - position_ -= is_horizontal ? size.width : size.height; + const PhysicalSize& size = line_info->LineEndFragment()->Size(); + inline_size = is_horizontal ? -size.width : -size.height; } if (fragment) { - const NGPhysicalSize& size = fragment->Size(); - position_ += is_horizontal ? size.width : size.height; + const PhysicalSize& size = fragment->Size(); + inline_size = is_horizontal ? size.width : size.height; } line_info->SetLineEndFragment(std::move(fragment)); + position_ += inline_size; + return inline_size; } // Compute the base direction for bidi algorithm for this line. @@ -273,8 +285,8 @@ void NGLineBreaker::ComputeBaseDirection() { void NGLineBreaker::PrepareNextLine(NGLineInfo* line_info) { // NGLineInfo is not supposed to be re-used becase it's not much gain and to // avoid rare code path. - NGInlineItemResults* item_results = line_info->MutableResults(); - DCHECK(item_results->IsEmpty()); + const NGInlineItemResults& item_results = line_info->Results(); + DCHECK(item_results.IsEmpty()); if (item_index_) { // We're past the first line @@ -310,6 +322,8 @@ void NGLineBreaker::PrepareNextLine(NGLineInfo* line_info) { // Use 'text-indent' as the initial position. This lets tab positions to align // regardless of 'text-indent'. position_ = line_info->TextIndent(); + + overflow_item_index_ = 0; } void NGLineBreaker::NextLine( @@ -326,9 +340,9 @@ void NGLineBreaker::NextLine( out_floats_for_min_max, line_info); RemoveTrailingCollapsibleSpace(line_info); - NGInlineItemResults* item_results = line_info->MutableResults(); + const NGInlineItemResults& item_results = line_info->Results(); #if DCHECK_IS_ON() - for (const auto& result : *item_results) + for (const auto& result : item_results) result.CheckConsistency(mode_ == NGLineBreakerMode::kMinContent); #endif @@ -340,7 +354,7 @@ void NGLineBreaker::NextLine( // // TODO(kojii): There are cases where we need to PlaceItems() without creating // line boxes. These cases need to be reviewed. - bool should_create_line_box = ShouldCreateLineBox(*item_results) || + bool should_create_line_box = ShouldCreateLineBox(item_results) || (has_list_marker_ && line_info->IsLastLine()) || mode_ != NGLineBreakerMode::kContent; @@ -377,7 +391,7 @@ void NGLineBreaker::BreakLine( return; } - NGInlineItemResults* item_results = line_info->MutableResults(); + const NGInlineItemResults& item_results = line_info->Results(); // Handle trailable items first. These items may not be break before. // They (or part of them) may also overhang the available width. @@ -388,11 +402,16 @@ void NGLineBreaker::BreakLine( else HandleEmptyText(item, line_info); #if DCHECK_IS_ON() - if (!item_results->IsEmpty()) - item_results->back().CheckConsistency(true); + if (!item_results.IsEmpty()) + item_results.back().CheckConsistency(true); #endif continue; } + if (item.Type() == NGInlineItem::kOpenTag) { + if (HandleOpenTag(item, line_info)) + continue; + return; + } if (item.Type() == NGInlineItem::kCloseTag) { HandleCloseTag(item, line_info); continue; @@ -413,12 +432,12 @@ void NGLineBreaker::BreakLine( // Items after this point are not trailable. Break at the earliest break // opportunity if we're trailing. if (state_ == LineBreakState::kTrailing && - CanBreakAfterLast(*item_results)) { + CanBreakAfterLast(item_results)) { // If the sticky images quirk is enabled, and this is an image that // follows text that doesn't end with something breakable, we cannot break // between the two items. if (sticky_images_quirk_ && - IsStickyImage(item, *item_results, trailing_whitespace_, Text())) { + IsStickyImage(item, item_results, trailing_whitespace_, Text())) { HandleAtomicInline(item, percentage_resolution_block_size_for_min_max, line_info); continue; @@ -431,8 +450,6 @@ void NGLineBreaker::BreakLine( if (item.Type() == NGInlineItem::kAtomicInline) { HandleAtomicInline(item, percentage_resolution_block_size_for_min_max, line_info); - } else if (item.Type() == NGInlineItem::kOpenTag) { - HandleOpenTag(item, line_info); } else if (item.Type() == NGInlineItem::kOutOfFlowPositioned) { AddItem(item, line_info); MoveToNextOf(item); @@ -465,6 +482,8 @@ void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const { line_info->SetWidth(available_width, position_); line_info->SetBfcOffset( {line_opportunity_.line_left_offset, line_opportunity_.bfc_block_offset}); + if (mode_ == NGLineBreakerMode::kContent) + line_info->UpdateTextAlign(); } void NGLineBreaker::HandleText(const NGInlineItem& item, @@ -477,7 +496,7 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, // If we're trailing, only trailing spaces can be included in this line. if (state_ == LineBreakState::kTrailing) { - if (CanBreakAfterLast(*line_info->MutableResults())) + if (CanBreakAfterLast(line_info->Results())) return HandleTrailingSpaces(item, shape_result, line_info); // When a run of preserved spaces are across items, |CanBreakAfterLast| is // false for between spaces. But we still need to handle them as trailing @@ -519,17 +538,18 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, // Try to break inside of this text item. LayoutUnit available_width = AvailableWidthToFit(); - BreakText(item_result, item, shape_result, available_width - position_, - line_info); - - LayoutUnit next_position = position_ + item_result->inline_size; - bool is_overflow = next_position > available_width; - DCHECK(is_overflow || item_result->shape_result); - position_ = next_position; - item_result->may_break_inside = !is_overflow; + BreakResult break_result = + BreakText(item_result, item, shape_result, available_width - position_, + line_info); + DCHECK(item_result->shape_result || + (break_result == kOverflow && break_anywhere_if_overflow_ && + !override_break_anywhere_)); + position_ += item_result->inline_size; + DCHECK_EQ(break_result == kSuccess, position_ <= available_width); + item_result->may_break_inside = break_result == kSuccess; MoveToNextOf(*item_result); - if (!is_overflow || + if (break_result == kSuccess || (state_ == LineBreakState::kTrailing && item_result->shape_result)) { if (item_result->end_offset < item.EndOffset()) { // The break point found, and text follows. Break here, after trailing @@ -542,15 +562,27 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, return; } + DCHECK_EQ(break_result, kOverflow); return HandleOverflow(line_info); } - // Add the whole item if !auto_wrap. The previous line should not have wrapped - // in the middle of nowrap item. - DCHECK_EQ(item_result->start_offset, item.StartOffset()); + // Add until the end of the item if !auto_wrap. In most cases, it's the whole + // item. DCHECK_EQ(item_result->end_offset, item.EndOffset()); - item_result->inline_size = shape_result.SnappedWidth().ClampNegativeToZero(); - item_result->shape_result = ShapeResultView::Create(&shape_result); + if (item_result->start_offset == item.StartOffset()) { + item_result->inline_size = + shape_result.SnappedWidth().ClampNegativeToZero(); + item_result->shape_result = ShapeResultView::Create(&shape_result); + } else { + // <wbr> can wrap even if !auto_wrap. Spaces after that will be leading + // spaces and thus be collapsed. + DCHECK(trailing_whitespace_ == WhitespaceState::kLeading && + item_result->start_offset >= item.StartOffset()); + item_result->shape_result = ShapeResultView::Create( + &shape_result, item_result->start_offset, item_result->end_offset); + item_result->inline_size = + item_result->shape_result->SnappedWidth().ClampNegativeToZero(); + } DCHECK(!item_result->may_break_inside); DCHECK(!item_result->can_break_after); @@ -559,21 +591,18 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, MoveToNextOf(item); } -void NGLineBreaker::BreakText(NGInlineItemResult* item_result, - const NGInlineItem& item, - const ShapeResult& item_shape_result, - LayoutUnit available_width, - NGLineInfo* line_info) { +NGLineBreaker::BreakResult NGLineBreaker::BreakText( + NGInlineItemResult* item_result, + const NGInlineItem& item, + const ShapeResult& item_shape_result, + LayoutUnit available_width, + NGLineInfo* line_info) { DCHECK(item.Type() == NGInlineItem::kText || (item.Type() == NGInlineItem::kControl && Text()[item.StartOffset()] == kTabulationCharacter)); DCHECK(&item_shape_result); item.AssertOffset(item_result->start_offset); - // TODO(kojii): We need to instantiate ShapingLineBreaker here because it - // has item-specific info as context. Should they be part of ShapeLine() to - // instantiate once, or is this just fine since instatiation is not - // expensive? DCHECK_EQ(item_shape_result.StartIndex(), item.StartOffset()); DCHECK_EQ(item_shape_result.EndIndex(), item.EndOffset()); struct ShapeCallbackContext { @@ -593,7 +622,6 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result, shape_callback, &shape_callback_context); if (!enable_soft_hyphen_) breaker.DisableSoftHyphen(); - available_width = std::max(LayoutUnit(0), available_width); // Use kStartShouldBeSafe if at the beginning of a line. unsigned options = ShapingLineBreaker::kDefaultOptions; @@ -612,36 +640,58 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result, if (break_anywhere_if_overflow_ && !override_break_anywhere_) options |= ShapingLineBreaker::kNoResultIfOverflow; - ShapingLineBreaker::Result result; - scoped_refptr<const ShapeResultView> shape_result = breaker.ShapeLine( - item_result->start_offset, available_width, options, &result); - - // If this item overflows and 'break-word' is set, this line will be - // rewinded. Making this item long enough to overflow is enough. - if (!shape_result) { - DCHECK(options & ShapingLineBreaker::kNoResultIfOverflow); - item_result->inline_size = available_width + 1; - item_result->end_offset = item.EndOffset(); - return; - } - DCHECK_EQ(shape_result->NumCharacters(), - result.break_offset - item_result->start_offset); - - if (result.is_hyphenated) { - SetLineEndFragment( - CreateHyphenFragment(node_, constraint_space_.GetWritingMode(), item), - line_info); - - // TODO(kojii): Implement when adding a hyphen caused overflow. - // crbug.com/714962: Should be removed when switched to NGPaint. - item_result->text_end_effect = NGTextEndEffect::kHyphen; +#if DCHECK_IS_ON() + unsigned try_count = 0; +#endif + LayoutUnit inline_size; + while (true) { +#if DCHECK_IS_ON() + ++try_count; + DCHECK_LE(try_count, 2u); +#endif + ShapingLineBreaker::Result result; + scoped_refptr<const ShapeResultView> shape_result = breaker.ShapeLine( + item_result->start_offset, available_width.ClampNegativeToZero(), + options, &result); + + // If this item overflows and 'break-word' is set, this line will be + // rewinded. Making this item long enough to overflow is enough. + if (!shape_result) { + DCHECK(options & ShapingLineBreaker::kNoResultIfOverflow); + item_result->inline_size = available_width + 1; + item_result->end_offset = item.EndOffset(); + return kOverflow; + } + DCHECK_EQ(shape_result->NumCharacters(), + result.break_offset - item_result->start_offset); + // It is critical to move the offset forward, or NGLineBreaker may keep + // adding NGInlineItemResult until all the memory is consumed. + CHECK_GT(result.break_offset, item_result->start_offset); + + inline_size = shape_result->SnappedWidth().ClampNegativeToZero(); + item_result->inline_size = inline_size; + if (UNLIKELY(result.is_hyphenated)) { + const WritingMode writing_mode = constraint_space_.GetWritingMode(); + scoped_refptr<const NGPhysicalTextFragment> hyphen_fragment = + CreateHyphenFragment(node_, writing_mode, item); + LayoutUnit space_for_hyphen = available_width - inline_size; + LayoutUnit hyphen_inline_size = IsHorizontalWritingMode(writing_mode) + ? hyphen_fragment->Size().width + : hyphen_fragment->Size().height; + // If the hyphen overflows, retry with the reduced available width. + if (space_for_hyphen >= 0 && hyphen_inline_size > space_for_hyphen) { + available_width -= hyphen_inline_size; + continue; + } + inline_size += SetLineEndFragment(std::move(hyphen_fragment), line_info); + item_result->text_end_effect = NGTextEndEffect::kHyphen; + } + item_result->inline_size = + shape_result->SnappedWidth().ClampNegativeToZero(); + item_result->end_offset = result.break_offset; + item_result->shape_result = std::move(shape_result); + break; } - item_result->inline_size = shape_result->SnappedWidth().ClampNegativeToZero(); - item_result->end_offset = result.break_offset; - item_result->shape_result = std::move(shape_result); - // It is critical to move offset forward, or NGLineBreaker may keep adding - // NGInlineItemResult until all the memory is consumed. - CHECK_GT(item_result->end_offset, item_result->start_offset) << Text(); // * If width <= available_width: // * If offset < item.EndOffset(): the break opportunity to fit is found. @@ -666,6 +716,7 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result, break_iterator_.IsBreakable(item_result->end_offset); trailing_whitespace_ = WhitespaceState::kUnknown; } + return inline_size <= available_width ? kSuccess : kOverflow; } // This function handles text item for min-content. The specialized logic is @@ -1121,10 +1172,9 @@ void NGLineBreaker::HandleAtomicInline( .LayoutAtomicInline(constraint_space_, node_.Style(), line_info->LineStyle().GetFontBaseline(), line_info->UseFirstLineStyle()); - DCHECK(item_result->layout_result->PhysicalFragment()); item_result->inline_size = NGFragment(constraint_space_.GetWritingMode(), - *item_result->layout_result->PhysicalFragment()) + item_result->layout_result->PhysicalFragment()) .InlineSize(); item_result->margins = ComputeLineMarginsForVisualContainer(constraint_space_, style); @@ -1199,7 +1249,7 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item, // fragmentainer break. In that case the floats associated with this line will // already have been processed. NGInlineItemResult* item_result = AddItem(item, line_info); - ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_); + item_result->can_break_after = auto_wrap_; MoveToNextOf(item); // If we are currently computing our min/max-content size simply append to @@ -1261,7 +1311,7 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item, // Check if we already have a pending float. That's because a float cannot be // higher than any block or floated box generated before. - if (HasUnpositionedFloats(*line_info->MutableResults()) || float_after_line) { + if (HasUnpositionedFloats(line_info->Results()) || float_after_line) { item_result->has_unpositioned_floats = true; } else { NGPositionedFloat positioned_float = PositionFloat( @@ -1276,12 +1326,13 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item, NGLayoutOpportunity opportunity = exclusion_space_->FindLayoutOpportunity( {constraint_space_.BfcOffset().line_offset, bfc_block_offset}, - constraint_space_.AvailableSize().inline_size, NGLogicalSize()); + constraint_space_.AvailableSize().inline_size, LogicalSize()); DCHECK_EQ(bfc_block_offset, opportunity.rect.BlockStartOffset()); line_opportunity_ = opportunity.ComputeLineLayoutOpportunity( constraint_space_, line_opportunity_.line_block_size, LayoutUnit()); + available_width_ = ComputeAvailableWidth(); DCHECK_GE(AvailableWidth(), LayoutUnit()); } @@ -1290,7 +1341,8 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item, bool NGLineBreaker::ComputeOpenTagResult( const NGInlineItem& item, const NGConstraintSpace& constraint_space, - NGInlineItemResult* item_result) { + NGInlineItemResult* item_result, + base::Optional<NGLineBoxStrut> margins) { DCHECK_EQ(item.Type(), NGInlineItem::kOpenTag); DCHECK(item.Style()); const ComputedStyle& style = *item.Style(); @@ -1301,7 +1353,9 @@ bool NGLineBreaker::ComputeOpenTagResult( item_result->borders = ComputeLineBorders(style); item_result->padding = ComputeLinePadding(constraint_space, style); if (item_result->has_edge) { - item_result->margins = ComputeLineMarginsForSelf(constraint_space, style); + item_result->margins = + margins ? *margins + : ComputeLineMarginsForSelf(constraint_space, style); item_result->inline_size = item_result->margins.inline_start + item_result->borders.inline_start + item_result->padding.inline_start; @@ -1311,11 +1365,39 @@ bool NGLineBreaker::ComputeOpenTagResult( return false; } -void NGLineBreaker::HandleOpenTag(const NGInlineItem& item, +bool NGLineBreaker::HandleOpenTag(const NGInlineItem& item, NGLineInfo* line_info) { + DCHECK_EQ(item.Type(), NGInlineItem::kOpenTag); + DCHECK(item.Style()); + const ComputedStyle& style = *item.Style(); + + // OpenTag is not trailable, except when it has negative inline-start margin, + // which can bring the position back to inside of the available width. + base::Optional<NGLineBoxStrut> margins; + if (UNLIKELY(state_ == LineBreakState::kTrailing && + CanBreakAfterLast(line_info->Results()))) { + bool can_continue = false; + if (UNLIKELY(item_index_ >= overflow_item_index_ && + item.ShouldCreateBoxFragment() && item.HasStartEdge() && + style.MayHaveMargin())) { + margins = ComputeLineMarginsForSelf(constraint_space_, style); + LayoutUnit inline_start_margin = margins->inline_start; + can_continue = inline_start_margin < 0 && + position_ + inline_start_margin < AvailableWidthToFit(); + } + if (!can_continue) { + // Not that case. Break the line before this OpenTag. + line_info->SetIsLastLine(false); + return false; + } + // The state is back to normal because the position is back to inside of the + // available width. + state_ = LineBreakState::kContinue; + } + NGInlineItemResult* item_result = AddItem(item, line_info); - if (ComputeOpenTagResult(item, constraint_space_, item_result)) { + if (ComputeOpenTagResult(item, constraint_space_, item_result, margins)) { position_ += item_result->inline_size; // While the spec defines "non-zero margins, padding, or borders" prevents @@ -1327,16 +1409,15 @@ void NGLineBreaker::HandleOpenTag(const NGInlineItem& item, } bool was_auto_wrap = auto_wrap_; - DCHECK(item.Style()); - const ComputedStyle& style = *item.Style(); SetCurrentStyle(style); MoveToNextOf(item); DCHECK(!item_result->can_break_after); - NGInlineItemResults* item_results = line_info->MutableResults(); - if (UNLIKELY(!was_auto_wrap && auto_wrap_ && item_results->size() >= 2)) { + const NGInlineItemResults& item_results = line_info->Results(); + if (UNLIKELY(!was_auto_wrap && auto_wrap_ && item_results.size() >= 2)) { ComputeCanBreakAfter(std::prev(item_result), auto_wrap_, break_iterator_); } + return true; } void NGLineBreaker::HandleCloseTag(const NGInlineItem& item, @@ -1360,8 +1441,8 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item, // If the line can break after the previous item, prohibit it and allow break // after this close tag instead. if (was_auto_wrap) { - NGInlineItemResults* item_results = line_info->MutableResults(); - if (item_results->size() >= 2) { + const NGInlineItemResults& item_results = line_info->Results(); + if (item_results.size() >= 2) { NGInlineItemResult* last = std::prev(item_result); item_result->can_break_after = last->can_break_after; last->can_break_after = false; @@ -1390,6 +1471,8 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item, // At this point, item_results does not fit into the current line, and there // are no break opportunities in item_results.back(). void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { + overflow_item_index_ = std::max(overflow_item_index_, item_index_); + // Compute the width needing to rewind. When |width_to_rewind| goes negative, // items can fit within the line. LayoutUnit available_width = AvailableWidthToFit(); @@ -1435,14 +1518,18 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { LayoutUnit item_available_width = std::min(-width_to_rewind, item_result->inline_size - 1); SetCurrentStyle(*item.Style()); - BreakText(item_result, item, *item.TextShapeResult(), - item_available_width, line_info); + BreakResult break_result = + BreakText(item_result, item, *item.TextShapeResult(), + item_available_width, line_info); #if DCHECK_IS_ON() item_result->CheckConsistency(true); #endif // If BreakText() changed this item small enough to fit, break here. - if (item_result->inline_size <= item_available_width) { - DCHECK(item_result->end_offset < item.EndOffset()); + DCHECK_EQ(break_result == kSuccess, + item_result->inline_size <= item_available_width); + if (break_result == kSuccess) { + DCHECK_LE(item_result->inline_size, item_available_width); + DCHECK_LT(item_result->end_offset, item.EndOffset()); DCHECK(item_result->can_break_after); DCHECK_LE(i + 1, item_results->size()); if (i + 1 == item_results->size()) { @@ -1478,6 +1565,7 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { if (!item_results->IsEmpty()) Rewind(0, line_info); state_ = LineBreakState::kContinue; + overflow_item_index_ = 0; return; } @@ -1508,6 +1596,8 @@ void NGLineBreaker::Rewind(unsigned new_end, NGLineInfo* line_info) { // most cases where our support for rewinding positioned floats is not great // yet (see below.) while (item_results[new_end].item->Type() == NGInlineItem::kFloating) { + // We assume floats can break after, or this may cause an infinite loop. + DCHECK(item_results[new_end].can_break_after); ++new_end; if (new_end == item_results.size()) { position_ = line_info->ComputeWidth(); @@ -1520,6 +1610,8 @@ void NGLineBreaker::Rewind(unsigned new_end, NGLineInfo* line_info) { for (unsigned i = item_results.size(); i > new_end;) { NGInlineItemResult& rewind = item_results[--i]; if (rewind.positioned_float) { + // We assume floats can break after, or this may cause an infinite loop. + DCHECK(rewind.can_break_after); // TODO(kojii): We do not have mechanism to remove once positioned floats // yet, and that rewinding them may lay it out twice. For now, prohibit // rewinding positioned floats. This may results in incorrect layout, but @@ -1560,7 +1652,7 @@ void NGLineBreaker::Rewind(unsigned new_end, NGLineInfo* line_info) { const ComputedStyle& NGLineBreaker::ComputeCurrentStyle( unsigned item_result_index, NGLineInfo* line_info) const { - NGInlineItemResults& item_results = *line_info->MutableResults(); + const NGInlineItemResults& item_results = line_info->Results(); // Use the current item if it can compute the current style. const NGInlineItem* item = item_results[item_result_index].item; @@ -1616,8 +1708,10 @@ void NGLineBreaker::SetCurrentStyle(const ComputedStyle& style) { line_break_type = LineBreakType::kKeepAll; break; } - if (UNLIKELY(override_break_anywhere_ && break_anywhere_if_overflow_)) + if (UNLIKELY((override_break_anywhere_ && break_anywhere_if_overflow_) || + style.GetLineBreak() == LineBreak::kAnywhere)) { line_break_type = LineBreakType::kBreakCharacter; + } break_iterator_.SetBreakType(line_break_type); enable_soft_hyphen_ = style.GetHyphens() != Hyphens::kNone; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h index 1e9baf8885b..17c3d0545a0 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h @@ -47,7 +47,7 @@ class CORE_EXPORT NGLineBreaker { // Compute the next line break point and produces NGInlineItemResults for // the line. inline void NextLine(NGLineInfo* line_info) { - NextLine(NGSizeIndefinite, nullptr, line_info); + NextLine(kIndefiniteSize, nullptr, line_info); } // During the min/max size calculation we need a special percentage @@ -72,9 +72,11 @@ class CORE_EXPORT NGLineBreaker { // Compute NGInlineItemResult for an open tag item. // Returns true if this item has edge and may have non-zero inline size. - static bool ComputeOpenTagResult(const NGInlineItem&, - const NGConstraintSpace&, - NGInlineItemResult*); + static bool ComputeOpenTagResult( + const NGInlineItem&, + const NGConstraintSpace&, + NGInlineItemResult*, + base::Optional<NGLineBoxStrut> margins = base::nullopt); // This enum is private, except for |WhitespaceStateForTesting()|. See // |whitespace_| member. @@ -98,8 +100,8 @@ class CORE_EXPORT NGLineBreaker { unsigned end_offset, NGLineInfo*); NGInlineItemResult* AddItem(const NGInlineItem&, NGLineInfo*); - void SetLineEndFragment(scoped_refptr<const NGPhysicalTextFragment>, - NGLineInfo*); + LayoutUnit SetLineEndFragment(scoped_refptr<const NGPhysicalTextFragment>, + NGLineInfo*); void BreakLine(LayoutUnit percentage_resolution_block_size_for_min_max, Vector<LayoutObject*>* out_floats_for_min_max, @@ -126,11 +128,12 @@ class CORE_EXPORT NGLineBreaker { HandleText(item, *item.TextShapeResult(), line_info); } void HandleText(const NGInlineItem& item, const ShapeResult&, NGLineInfo*); - void BreakText(NGInlineItemResult*, - const NGInlineItem&, - const ShapeResult&, - LayoutUnit available_width, - NGLineInfo*); + enum BreakResult { kSuccess, kOverflow }; + BreakResult BreakText(NGInlineItemResult*, + const NGInlineItem&, + const ShapeResult&, + LayoutUnit available_width, + NGLineInfo*); bool HandleTextForFastMinContent(NGInlineItemResult*, const NGInlineItem&, const ShapeResult&, @@ -163,7 +166,7 @@ class CORE_EXPORT NGLineBreaker { Vector<LayoutObject*>* out_floats_for_min_max, NGLineInfo*); - void HandleOpenTag(const NGInlineItem&, NGLineInfo*); + bool HandleOpenTag(const NGInlineItem&, NGLineInfo*); void HandleCloseTag(const NGInlineItem&, NGLineInfo*); void HandleOverflow(NGLineInfo*); @@ -179,11 +182,13 @@ class CORE_EXPORT NGLineBreaker { void ComputeBaseDirection(); LayoutUnit AvailableWidth() const { - return line_opportunity_.AvailableInlineSize(); + DCHECK_EQ(available_width_, ComputeAvailableWidth()); + return available_width_; } LayoutUnit AvailableWidthToFit() const { return AvailableWidth().AddEpsilon(); } + LayoutUnit ComputeAvailableWidth() const; // Represents the current offset of the input. LineBreakState state_; @@ -197,6 +202,7 @@ class CORE_EXPORT NGLineBreaker { // The current position from inline_start. Unlike NGInlineLayoutAlgorithm // that computes position in visual order, this position in logical order. LayoutUnit position_; + LayoutUnit available_width_; NGLineLayoutOpportunity line_opportunity_; NGInlineNode node_; @@ -261,6 +267,9 @@ class CORE_EXPORT NGLineBreaker { }; base::Optional<TrailingCollapsibleSpace> trailing_collapsible_space_; + // Keep track of item index where overflow occurrred. + unsigned overflow_item_index_; + // Keep track of handled float items. See HandleFloat(). const NGPositionedFloatVector& leading_floats_; unsigned leading_floats_index_ = 0u; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc index 861fd965e43..778bb892eec 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc @@ -35,10 +35,10 @@ class NGLineBreakerTest : public NGLayoutTest { node.PrepareLayoutIfNeeded(); NGConstraintSpace space = - NGConstraintSpaceBuilder( - WritingMode::kHorizontalTb, WritingMode::kHorizontalTb, - /* is_new_fc */ false) - .SetAvailableSize({available_width, NGSizeIndefinite}) + NGConstraintSpaceBuilder(WritingMode::kHorizontalTb, + WritingMode::kHorizontalTb, + /* is_new_fc */ false) + .SetAvailableSize({available_width, kIndefiniteSize}) .ToConstraintSpace(); scoped_refptr<NGInlineBreakToken> break_token; @@ -494,8 +494,13 @@ TEST_P(NGTrailingSpaceWidthTest, TrailingSpaceWidth) { )HTML"); Vector<NGLineInfo> line_infos = BreakToLineInfo(node, LayoutUnit(50)); - EXPECT_EQ(line_infos[0].ComputeTrailingSpaceWidth(), - LayoutUnit(10) * data.trailing_space_width); + const NGLineInfo& line_info = line_infos[0]; + if (line_info.ShouldHangTrailingSpaces()) { + EXPECT_EQ(line_info.HangWidth(), + LayoutUnit(10) * data.trailing_space_width); + } else { + EXPECT_EQ(line_info.HangWidth(), LayoutUnit()); + } } TEST_F(NGLineBreakerTest, MinMaxWithTrailingSpaces) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc index 97a947e04a5..747f3f13a07 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc @@ -13,29 +13,6 @@ namespace blink { -namespace { - -// Create the style to use for the ellipsis characters. -// -// The ellipsis is styled according to the line style. -// https://drafts.csswg.org/css-ui/#ellipsing-details -scoped_refptr<const ComputedStyle> CreateEllipsisStyle( - scoped_refptr<const ComputedStyle> line_style) { - if (line_style->TextDecorationsInEffect() == TextDecoration::kNone) - return line_style; - - // Ellipsis should not have text decorations. Reset if it's set. - // This is not defined, but 4 impls do this. - scoped_refptr<ComputedStyle> ellipsis_style = - ComputedStyle::CreateAnonymousStyleWithDisplay(*line_style, - EDisplay::kInline); - ellipsis_style->ResetTextDecoration(); - ellipsis_style->ClearAppliedTextDecorations(); - return ellipsis_style; -} - -} // namespace - NGLineTruncator::NGLineTruncator(NGInlineNode& node, const NGLineInfo& line_info) : node_(node), @@ -47,8 +24,9 @@ LayoutUnit NGLineTruncator::TruncateLine( LayoutUnit line_width, NGLineBoxFragmentBuilder::ChildList* line_box) { // Shape the ellipsis and compute its inline size. - scoped_refptr<const ComputedStyle> ellipsis_style = - CreateEllipsisStyle(line_style_); + // The ellipsis is styled according to the line style. + // https://drafts.csswg.org/css-ui/#ellipsing-details + const ComputedStyle* ellipsis_style = line_style_.get(); const Font& font = ellipsis_style->GetFont(); const SimpleFontData* font_data = font.PrimaryFont(); DCHECK(font_data); @@ -101,7 +79,7 @@ LayoutUnit NGLineTruncator::TruncateLine( NGTextFragmentBuilder builder(node_, line_style_->GetWritingMode()); DCHECK(ellipsized_fragment->GetLayoutObject() && ellipsized_fragment->GetLayoutObject()->IsInline()); - builder.SetText(ellipsized_fragment->GetLayoutObject(), ellipsis_text, + builder.SetText(ellipsized_fragment->GetMutableLayoutObject(), ellipsis_text, ellipsis_style, true /* is_ellipsis_style */, std::move(ellipsis_shape_result)); FontBaseline baseline_type = line_style_->GetFontBaseline(); @@ -109,7 +87,7 @@ LayoutUnit NGLineTruncator::TruncateLine( baseline_type); line_box->AddChild( builder.ToTextFragment(), - NGLogicalOffset{ellipsis_inline_offset, -ellipsis_metrics.ascent}, + LogicalOffset{ellipsis_inline_offset, -ellipsis_metrics.ascent}, ellipsis_width, 0); return std::max(ellipsis_inline_offset + ellipsis_width, line_width); } @@ -121,10 +99,9 @@ void NGLineTruncator::HideChild(NGLineBoxFragmentBuilder::Child* child) { const NGPhysicalFragment* fragment = nullptr; if (const NGLayoutResult* layout_result = child->layout_result.get()) { // Need to propagate OOF descendants in this inline-block child. - if (!layout_result->OutOfFlowPositionedDescendants().IsEmpty()) { + if (layout_result->PhysicalFragment().HasOutOfFlowPositionedDescendants()) return; - } - fragment = layout_result->PhysicalFragment(); + fragment = &layout_result->PhysicalFragment(); } else { fragment = child->fragment.get(); } @@ -211,6 +188,11 @@ bool NGLineTruncator::TruncateChild(LayoutUnit space_for_child, if (!child->fragment) return is_first_child; auto& fragment = To<NGPhysicalTextFragment>(*child->fragment); + + // No need to truncate empty results. + if (!fragment.TextShapeResult()) + return is_first_child; + // TODO(layout-dev): Add support for OffsetToFit to ShapeResultView to avoid // this copy. scoped_refptr<blink::ShapeResult> shape_result = diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc index fab78c10ead..1e7fb8e2a92 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc @@ -12,7 +12,6 @@ #include "third_party/blink/renderer/core/editing/ephemeral_range.h" #include "third_party/blink/renderer/core/editing/position.h" #include "third_party/blink/renderer/core/layout/layout_text_fragment.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" #include "third_party/blink/renderer/platform/text/character.h" @@ -27,16 +26,16 @@ bool CanUseNGOffsetMapping(const LayoutObject& object) { } Position CreatePositionForOffsetMapping(const Node& node, unsigned dom_offset) { - if (node.IsTextNode()) { + if (auto* text_node = DynamicTo<Text>(node)) { // 'text-transform' may make the rendered text length longer than the // original text node, in which case we clamp the offset to avoid crashing. // TODO(crbug.com/750990): Support 'text-transform' to remove this hack. #if DCHECK_IS_ON() // Ensures that the clamping hack kicks in only with text-transform. if (node.ComputedStyleRef().TextTransform() == ETextTransform::kNone) - DCHECK_LE(dom_offset, ToText(node).length()); + DCHECK_LE(dom_offset, text_node->length()); #endif - const unsigned clamped_offset = std::min(dom_offset, ToText(node).length()); + const unsigned clamped_offset = std::min(dom_offset, text_node->length()); return Position(&node, clamped_offset); } // For non-text-anchored position, the offset must be either 0 or 1. @@ -46,13 +45,13 @@ Position CreatePositionForOffsetMapping(const Node& node, unsigned dom_offset) { std::pair<const Node&, unsigned> ToNodeOffsetPair(const Position& position) { DCHECK(NGOffsetMapping::AcceptsPosition(position)) << position; - if (position.AnchorNode()->IsTextNode()) { + if (auto* text_node = DynamicTo<Text>(position.AnchorNode())) { if (position.IsOffsetInAnchor()) return {*position.AnchorNode(), position.OffsetInContainerNode()}; if (position.IsBeforeAnchor()) return {*position.AnchorNode(), 0}; DCHECK(position.IsAfterAnchor()); - return {*position.AnchorNode(), ToText(position.AnchorNode())->length()}; + return {*position.AnchorNode(), text_node->length()}; } if (position.IsBeforeAnchor()) return {*position.AnchorNode(), 0}; @@ -99,7 +98,29 @@ NGOffsetMappingUnit::NGOffsetMappingUnit(NGOffsetMappingUnitType type, dom_start_(dom_start), dom_end_(dom_end), text_content_start_(text_content_start), - text_content_end_(text_content_end) {} + text_content_end_(text_content_end) { + AssertValid(); +} + +void NGOffsetMappingUnit::AssertValid() const { +#if ENABLE_SECURITY_ASSERT + SECURITY_DCHECK(dom_start_ <= dom_end_) << dom_start_ << " vs. " << dom_end_; + SECURITY_DCHECK(text_content_start_ <= text_content_end_) + << text_content_start_ << " vs. " << text_content_end_; + if (layout_object_->IsText()) { + const LayoutText& layout_text = ToLayoutText(*layout_object_); + const unsigned text_start = + AssociatedNode() ? layout_text.TextStartOffset() : 0; + const unsigned text_end = text_start + layout_text.TextLength(); + SECURITY_DCHECK(dom_end_ >= text_start) + << dom_end_ << " vs. " << text_start; + SECURITY_DCHECK(dom_end_ <= text_end) << dom_end_ << " vs. " << text_end; + } else { + SECURITY_DCHECK(dom_start_ == 0) << dom_start_; + SECURITY_DCHECK(dom_end_ == 1) << dom_end_; + } +#endif +} NGOffsetMappingUnit::~NGOffsetMappingUnit() = default; @@ -238,7 +259,23 @@ LayoutBlockFlow* NGOffsetMapping::GetInlineFormattingContextOf( NGOffsetMapping::NGOffsetMapping(UnitVector&& units, RangeMap&& ranges, String text) - : units_(std::move(units)), ranges_(std::move(ranges)), text_(text) {} + : units_(std::move(units)), ranges_(std::move(ranges)), text_(text) { +#if ENABLE_SECURITY_ASSERT + for (const auto& unit : units_) { + SECURITY_DCHECK(unit.TextContentStart() <= text.length()) + << unit.TextContentStart() << "<=" << text.length(); + SECURITY_DCHECK(unit.TextContentEnd() <= text.length()) + << unit.TextContentEnd() << "<=" << text.length(); + unit.AssertValid(); + } + for (const auto& pair : ranges) { + SECURITY_DCHECK(pair.value.first < units_.size()) + << pair.value.first << "<" << units_.size(); + SECURITY_DCHECK(pair.value.second < units_.size()) + << pair.value.second << "<" << units_.size(); + } +#endif +} NGOffsetMapping::~NGOffsetMapping() = default; @@ -385,7 +422,7 @@ Position NGOffsetMapping::StartOfNextNonCollapsedContent( const auto node_and_offset = ToNodeOffsetPair(position); const Node& node = node_and_offset.first; const unsigned offset = node_and_offset.second; - while (unit != units_.end() && unit->GetOwner() == node) { + while (unit != units_.end() && unit->AssociatedNode() == node) { if (unit->DOMEnd() > offset && unit->GetType() != NGOffsetMappingUnitType::kCollapsed) { const unsigned result = std::max(offset, unit->DOMStart()); @@ -406,7 +443,7 @@ Position NGOffsetMapping::EndOfLastNonCollapsedContent( const auto node_and_offset = ToNodeOffsetPair(position); const Node& node = node_and_offset.first; const unsigned offset = node_and_offset.second; - while (unit->GetOwner() == node) { + while (unit->AssociatedNode() == node) { if (unit->DOMStart() < offset && unit->GetType() != NGOffsetMappingUnitType::kCollapsed) { const unsigned result = std::min(offset, unit->DOMEnd()); @@ -509,16 +546,6 @@ Position NGOffsetMapping::GetLastPosition(unsigned offset) const { return CreatePositionForOffsetMapping(node, dom_offset); } -PositionWithAffinity NGOffsetMapping::GetPositionWithAffinity( - const NGCaretNavigator::Position& position) const { - if (position.IsBeforeCharacter()) { - return PositionWithAffinity(GetLastPosition(position.index), - TextAffinity::kDownstream); - } - return PositionWithAffinity(GetFirstPosition(position.index + 1), - TextAffinity::kUpstream); -} - bool NGOffsetMapping::HasBidiControlCharactersOnly(unsigned start, unsigned end) const { DCHECK_LE(start, end); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h index a8b59f75f1e..743dd9d8757 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h @@ -9,7 +9,6 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/dom/node.h" #include "third_party/blink/renderer/core/editing/forward.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h" #include "third_party/blink/renderer/platform/heap/handle.h" #include "third_party/blink/renderer/platform/heap/persistent.h" #include "third_party/blink/renderer/platform/wtf/allocator.h" @@ -73,6 +72,8 @@ class CORE_EXPORT NGOffsetMappingUnit { unsigned ConvertTextContentToFirstDOMOffset(unsigned) const; unsigned ConvertTextContentToLastDOMOffset(unsigned) const; + void AssertValid() const; + private: NGOffsetMappingUnitType type_ = NGOffsetMappingUnitType::kIdentity; @@ -82,6 +83,10 @@ class CORE_EXPORT NGOffsetMappingUnit { // offset in |LayoutText::text_| instead of DOM node. unsigned dom_start_; unsigned dom_end_; + + // |text_content_start_| and |text_content_end_| are offsets in + // |NGOffsetMapping::text_|. These values are in [0, |text_.length()] to + // represent collapsed spaces at the end of block. unsigned text_content_start_; unsigned text_content_end_; @@ -219,13 +224,6 @@ class CORE_EXPORT NGOffsetMapping { Position GetFirstPosition(unsigned) const; Position GetLastPosition(unsigned) const; - // Converts the given caret position on text content to a PositionWithAffinity - // in DOM. If |position| is before a character, the function creates a - // downstream position before |GetLastPosition()| of the character; otherwise, - // it returns an upstream position after |GetFirstPosition()| of the character - PositionWithAffinity GetPositionWithAffinity( - const NGCaretNavigator::Position& position) const; - // Returns all NGOffsetMappingUnits whose text content ranges has non-empty // (but possibly collapsed) intersection with (start, end). Note that units // that only "touch" |start| or |end| are excluded. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc index 8969d67f8f5..9524bfb97ac 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc @@ -7,7 +7,6 @@ #include <utility> #include "third_party/blink/renderer/core/layout/layout_text.h" #include "third_party/blink/renderer/core/layout/layout_text_fragment.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc index f41b357b915..78b74c7b685 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc @@ -1225,6 +1225,33 @@ TEST_F(NGOffsetMappingTest, SoftHyphen) { TEST_RANGE(mapping.GetRanges(), text, 0u, 1u); } +// For http://crbug.com/965353 +TEST_F(NGOffsetMappingTest, PreWrapAndReusing) { + // Note: "white-space: break-space" yields same result. + SetupHtml("t", "<p id='t' style='white-space: pre-wrap'>abc</p>"); + Element& target = *GetDocument().getElementById("t"); + + // Change to <p id=t>abc xyz</p> + Text& text = *Text::Create(GetDocument(), " xyz"); + target.appendChild(&text); + UpdateAllLifecyclePhasesForTest(); + + // Change to <p id=t> xyz</p>. We attempt to reuse " xyz". + target.firstChild()->remove(); + UpdateAllLifecyclePhasesForTest(); + + const NGOffsetMapping& mapping = GetOffsetMapping(); + EXPECT_EQ(String(u" \u200Bxyz"), mapping.GetText()) + << "We have ZWS after leading preserved space."; + EXPECT_EQ((Vector<NGOffsetMappingUnit>{ + NGOffsetMappingUnit(kIdentity, *text.GetLayoutObject(), 0u, 1u, + 0u, 1u), + NGOffsetMappingUnit(kIdentity, *text.GetLayoutObject(), 1u, 4u, + 2u, 5u), + }), + mapping.GetUnits()); +} + TEST_F(NGOffsetMappingTest, TextOverflowEllipsis) { LoadAhem(); SetupHtml("t", @@ -1241,6 +1268,41 @@ TEST_F(NGOffsetMappingTest, TextOverflowEllipsis) { TEST_RANGE(mapping.GetRanges(), text, 0u, 1u); } +// https://crbug.com/967106 +TEST_F(NGOffsetMappingTest, StartOfNextNonCollapsedContentWithPseudo) { + // The white spaces are necessary for bug repro. Do not remove them. + SetupHtml("t", R"HTML( + <style>span#quote::before { content: '"'}</style> + <div id=t> + <span>foo </span> + <span id=quote>bar</span> + </div>)HTML"); + + const Element* quote = GetElementById("quote"); + const Node* text = quote->previousSibling(); + const Position position = Position::FirstPositionInNode(*text); + + EXPECT_EQ(Position(), + GetOffsetMapping().StartOfNextNonCollapsedContent(position)); +} + +// https://crbug.com/967106 +TEST_F(NGOffsetMappingTest, EndOfLastNonCollapsedContentWithPseudo) { + // The white spaces are necessary for bug repro. Do not remove them. + SetupHtml("t", R"HTML( + <style>span#quote::after { content: '" '}</style> + <div id=t> + <span id=quote>foo</span> + <span>bar</span> + </div>)HTML"); + + const Element* quote = GetElementById("quote"); + const Node* text = quote->nextSibling(); + const Position position = Position::LastPositionInNode(*text); + + EXPECT_EQ(Position(), + GetOffsetMapping().EndOfLastNonCollapsedContent(position)); +} // Test |GetOffsetMapping| which is available both for LayoutNG and for legacy. class NGOffsetMappingGetterTest : public RenderingTest, public testing::WithParamInterface<bool>, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc index bcbbeb51ba0..08681856ab1 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc @@ -4,7 +4,9 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" +#include "third_party/blink/renderer/core/editing/editing_utilities.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h" @@ -15,7 +17,6 @@ namespace blink { namespace { struct SameSizeAsNGPhysicalLineBoxFragment : NGPhysicalContainerFragment { - void* pointer; NGLineHeightMetrics metrics; }; @@ -23,6 +24,18 @@ static_assert(sizeof(NGPhysicalLineBoxFragment) == sizeof(SameSizeAsNGPhysicalLineBoxFragment), "NGPhysicalLineBoxFragment should stay small"); +bool IsInlineLeaf(const NGPhysicalFragment& fragment) { + if (fragment.IsText()) + return true; + return fragment.IsBox() && fragment.IsAtomicInline(); +} + +bool IsEditableFragment(const NGPhysicalFragment& fragment) { + if (!fragment.GetNode()) + return false; + return HasEditableStyle(*fragment.GetNode()); +} + } // namespace scoped_refptr<const NGPhysicalLineBoxFragment> @@ -34,7 +47,7 @@ NGPhysicalLineBoxFragment::Create(NGLineBoxFragmentBuilder* builder) { // we pass the buffer as a constructor argument. void* data = ::WTF::Partitions::FastMalloc( sizeof(NGPhysicalLineBoxFragment) + - builder->children_.size() * sizeof(NGLinkStorage), + builder->children_.size() * sizeof(NGLink), ::WTF::GetStringWithTypeName<NGPhysicalLineBoxFragment>()); new (data) NGPhysicalLineBoxFragment(builder); return base::AdoptRef(static_cast<NGPhysicalLineBoxFragment*>(data)); @@ -48,8 +61,13 @@ NGPhysicalLineBoxFragment::NGPhysicalLineBoxFragment( kFragmentLineBox, builder->line_box_type_), metrics_(builder->metrics_) { - style_ = std::move(builder->style_); + // A line box must have a metrics unless it's an empty line box. + DCHECK(!metrics_.IsEmpty() || IsEmptyLineBox()); base_direction_ = static_cast<unsigned>(builder->base_direction_); + has_hanging_ = builder->hang_inline_size_ != 0; + has_propagated_descendants_ = has_floating_descendants_ || + HasOutOfFlowPositionedDescendants() || + builder->unpositioned_list_marker_; } NGLineHeightMetrics NGPhysicalLineBoxFragment::BaselineMetrics( @@ -60,17 +78,35 @@ NGLineHeightMetrics NGPhysicalLineBoxFragment::BaselineMetrics( return metrics_; } -NGPhysicalOffsetRect NGPhysicalLineBoxFragment::ScrollableOverflow( +PhysicalRect NGPhysicalLineBoxFragment::ScrollableOverflow( const LayoutObject* container, const ComputedStyle* container_style, - NGPhysicalSize container_physical_size) const { + PhysicalSize container_physical_size) const { WritingMode container_writing_mode = container_style->GetWritingMode(); TextDirection container_direction = container_style->Direction(); - NGPhysicalOffsetRect overflow({}, Size()); + PhysicalRect overflow({}, Size()); for (const auto& child : Children()) { - NGPhysicalOffsetRect child_scroll_overflow = + PhysicalRect child_scroll_overflow = child->ScrollableOverflowForPropagation(container); child_scroll_overflow.offset += child.Offset(); + + // Chop the hanging part from scrollable overflow. Children overflow in + // inline direction should hang, which should not cause scroll. + // TODO(kojii): Should move to text fragment to make this more accurate. + if (UNLIKELY(has_hanging_ && !child->IsFloatingOrOutOfFlowPositioned())) { + if (IsHorizontalWritingMode(container_writing_mode)) { + if (child_scroll_overflow.offset.left < 0) + child_scroll_overflow.offset.left = LayoutUnit(); + if (child_scroll_overflow.Right() > Size().width) + child_scroll_overflow.ShiftRightEdgeTo(Size().width); + } else { + if (child_scroll_overflow.offset.top < 0) + child_scroll_overflow.offset.top = LayoutUnit(); + if (child_scroll_overflow.Bottom() > Size().height) + child_scroll_overflow.ShiftBottomEdgeTo(Size().height); + } + } + // If child has the same style as parent, parent will compute relative // offset. if (&child->Style() != container_style) { @@ -130,20 +166,63 @@ bool NGPhysicalLineBoxFragment::HasSoftWrapToNextLine() const { return !break_token.IsFinished() && !break_token.IsForcedBreak(); } -NGPhysicalOffset NGPhysicalLineBoxFragment::LineStartPoint() const { - const NGLogicalOffset logical_start; // (0, 0) - const NGPhysicalSize pixel_size(LayoutUnit(1), LayoutUnit(1)); +PhysicalOffset NGPhysicalLineBoxFragment::LineStartPoint() const { + const LogicalOffset logical_start; // (0, 0) + const PhysicalSize pixel_size(LayoutUnit(1), LayoutUnit(1)); return logical_start.ConvertToPhysical(Style().GetWritingMode(), BaseDirection(), Size(), pixel_size); } -NGPhysicalOffset NGPhysicalLineBoxFragment::LineEndPoint() const { +PhysicalOffset NGPhysicalLineBoxFragment::LineEndPoint() const { const LayoutUnit inline_size = NGFragment(Style().GetWritingMode(), *this).InlineSize(); - const NGLogicalOffset logical_end(inline_size, LayoutUnit()); - const NGPhysicalSize pixel_size(LayoutUnit(1), LayoutUnit(1)); + const LogicalOffset logical_end(inline_size, LayoutUnit()); + const PhysicalSize pixel_size(LayoutUnit(1), LayoutUnit(1)); return logical_end.ConvertToPhysical(Style().GetWritingMode(), BaseDirection(), Size(), pixel_size); } +const LayoutObject* NGPhysicalLineBoxFragment::ClosestLeafChildForPoint( + const PhysicalOffset& point, + bool only_editable_leaves) const { + const PhysicalSize unit_square(LayoutUnit(1), LayoutUnit(1)); + const LogicalOffset logical_point = point.ConvertToLogical( + Style().GetWritingMode(), BaseDirection(), Size(), unit_square); + const LayoutUnit inline_offset = logical_point.inline_offset; + const NGPhysicalFragment* closest_leaf_child = nullptr; + LayoutUnit closest_leaf_distance; + for (const auto& descendant : + NGInlineFragmentTraversal::DescendantsOf(*this)) { + const NGPhysicalFragment& fragment = *descendant.fragment; + if (!fragment.GetLayoutObject()) + continue; + if (!IsInlineLeaf(fragment) || fragment.IsListMarker()) + continue; + if (only_editable_leaves && !IsEditableFragment(fragment)) + continue; + + const LogicalSize fragment_logical_size = + fragment.Size().ConvertToLogical(Style().GetWritingMode()); + const LogicalOffset fragment_logical_offset = + descendant.offset_to_container_box.ConvertToLogical( + Style().GetWritingMode(), BaseDirection(), Size(), fragment.Size()); + const LayoutUnit inline_min = fragment_logical_offset.inline_offset; + const LayoutUnit inline_max = fragment_logical_offset.inline_offset + + fragment_logical_size.inline_size; + if (inline_offset >= inline_min && inline_offset < inline_max) + return fragment.GetLayoutObject(); + + const LayoutUnit distance = + inline_offset < inline_min ? inline_min - inline_offset + : inline_offset - inline_max + LayoutUnit(1); + if (!closest_leaf_child || distance < closest_leaf_distance) { + closest_leaf_child = &fragment; + closest_leaf_distance = distance; + } + } + if (!closest_leaf_child) + return nullptr; + return closest_leaf_child->GetLayoutObject(); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h index a295aacc7a3..7a453e31e5d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h @@ -32,7 +32,7 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final NGLineBoxFragmentBuilder* builder); ~NGPhysicalLineBoxFragment() { - for (const NGLinkStorage& child : Children()) + for (const NGLink& child : Children()) child.fragment->Release(); } @@ -41,8 +41,9 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final } bool IsEmptyLineBox() const { return LineBoxType() == kEmptyLineBox; } + // True if descendants were propagated to outside of this fragment. + bool HasPropagatedDescendants() const { return has_propagated_descendants_; } - const ComputedStyle& Style() const { return *style_; } const NGLineHeightMetrics& Metrics() const { return metrics_; } // The base direction of this line. Also known as the paragraph direction. @@ -59,20 +60,22 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final // ScrollableOverflow is not precomputed/cached because it cannot be computed // when LineBox is generated because it needs container dimensions to // resolve relative position of its children. - NGPhysicalOffsetRect ScrollableOverflow( - const LayoutObject* container, - const ComputedStyle* container_style, - NGPhysicalSize container_physical_size) const; + PhysicalRect ScrollableOverflow(const LayoutObject* container, + const ComputedStyle* container_style, + PhysicalSize container_physical_size) const; // Returns the first/last leaf fragment in the line in logical order. Returns // nullptr if the line box is empty. const NGPhysicalFragment* FirstLogicalLeaf() const; const NGPhysicalFragment* LastLogicalLeaf() const; + const LayoutObject* ClosestLeafChildForPoint(const PhysicalOffset&, + bool only_editable_leaves) const; + // Returns a point at the visual start/end of the line. // Encapsulates the handling of text direction and writing mode. - NGPhysicalOffset LineStartPoint() const; - NGPhysicalOffset LineEndPoint() const; + PhysicalOffset LineStartPoint() const; + PhysicalOffset LineEndPoint() const; // Whether the content soft-wraps to the next line. bool HasSoftWrapToNextLine() const; @@ -80,9 +83,8 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final private: NGPhysicalLineBoxFragment(NGLineBoxFragmentBuilder* builder); - scoped_refptr<const ComputedStyle> style_; NGLineHeightMetrics metrics_; - NGLinkStorage children_[]; + NGLink children_[]; }; template <> diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc index 26ca7c236bf..53167624896 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc @@ -16,7 +16,7 @@ class NGPhysicalLineBoxFragmentTest : public NGLayoutTest { NGPhysicalLineBoxFragmentTest() : NGLayoutTest() {} protected: - const NGPhysicalLineBoxFragment* GetLineBox() const { + Vector<const NGPhysicalLineBoxFragment*> GetLineBoxes() const { const Element* container = GetElementById("root"); DCHECK(container); const LayoutObject* layout_object = container->GetLayoutObject(); @@ -26,12 +26,21 @@ class NGPhysicalLineBoxFragmentTest : public NGLayoutTest { To<LayoutBlockFlow>(layout_object)->CurrentFragment(); DCHECK(root_fragment) << container; + Vector<const NGPhysicalLineBoxFragment*> lines; for (const auto& child : NGInlineFragmentTraversal::DescendantsOf(*root_fragment)) { - if (child.fragment->IsLineBox()) - return To<NGPhysicalLineBoxFragment>(child.fragment.get()); + if (const NGPhysicalLineBoxFragment* line = + DynamicTo<NGPhysicalLineBoxFragment>(child.fragment.get())) { + lines.push_back(line); + } } - NOTREACHED(); + return lines; + } + + const NGPhysicalLineBoxFragment* GetLineBox() const { + Vector<const NGPhysicalLineBoxFragment*> lines = GetLineBoxes(); + if (!lines.IsEmpty()) + return lines.front(); return nullptr; } }; @@ -47,6 +56,42 @@ class NGPhysicalLineBoxFragmentTest : public NGLayoutTest { EXPECT_EQ(GetElementById(id), fragment->GetNode()); \ } +TEST_F(NGPhysicalLineBoxFragmentTest, HasPropagatedDescendantsFloat) { + SetBodyInnerHTML(R"HTML( + <!DOCTYPE html> + <style> + div { + font-size: 10px; + width: 10ch; + } + .float { float: left; } + </style> + <div id=root>12345678 12345<div class=float>float</div></div> + )HTML"); + Vector<const NGPhysicalLineBoxFragment*> lines = GetLineBoxes(); + EXPECT_EQ(lines.size(), 2u); + EXPECT_FALSE(lines[0]->HasPropagatedDescendants()); + EXPECT_TRUE(lines[1]->HasPropagatedDescendants()); +} + +TEST_F(NGPhysicalLineBoxFragmentTest, HasPropagatedDescendantsOOF) { + SetBodyInnerHTML(R"HTML( + <!DOCTYPE html> + <style> + div { + font-size: 10px; + width: 10ch; + } + .abspos { position: absolute; } + </style> + <div id=root>12345678 12345<div class=abspos>abspos</div></div> + )HTML"); + Vector<const NGPhysicalLineBoxFragment*> lines = GetLineBoxes(); + EXPECT_EQ(lines.size(), 2u); + EXPECT_FALSE(lines[0]->HasPropagatedDescendants()); + EXPECT_TRUE(lines[1]->HasPropagatedDescendants()); +} + TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafInSimpleText) { SetBodyInnerHTML( "<div id=root>" diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc index 6db5844ca0f..384bb007a72 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc @@ -5,12 +5,13 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" #include "third_party/blink/renderer/core/dom/node.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" #include "third_party/blink/renderer/core/layout/layout_text_fragment.h" #include "third_party/blink/renderer/core/layout/line/line_orientation_utils.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h" +#include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" @@ -19,24 +20,15 @@ namespace blink { namespace { struct SameSizeAsNGPhysicalTextFragment : NGPhysicalFragment { - void* pointers[3]; + void* pointers[2]; unsigned offsets[2]; + PhysicalRect rect; }; static_assert(sizeof(NGPhysicalTextFragment) == sizeof(SameSizeAsNGPhysicalTextFragment), "NGPhysicalTextFragment should stay small"); -inline bool IsPhysicalTextFragmentAnonymousText( - const LayoutObject* layout_object) { - if (!layout_object) - return false; - if (layout_object->IsText() && ToLayoutText(layout_object)->IsTextFragment()) - return !ToLayoutTextFragment(layout_object)->AssociatedTextNode(); - const Node* node = layout_object->GetNode(); - return !node || node->IsPseudoElement(); -} - NGLineOrientation ToLineOrientation(WritingMode writing_mode) { switch (writing_mode) { case WritingMode::kHorizontalTb: @@ -59,15 +51,14 @@ NGPhysicalTextFragment::NGPhysicalTextFragment( unsigned start_offset, unsigned end_offset, scoped_refptr<const ShapeResultView> shape_result) - : NGPhysicalFragment(source.GetLayoutObject(), - source.StyleVariant(), - source.IsHorizontal() - ? NGPhysicalSize{shape_result->SnappedWidth(), - source.Size().height} - : NGPhysicalSize{source.Size().width, - shape_result->SnappedWidth()}, - kFragmentText, - source.TextType()), + : NGPhysicalFragment( + source.GetMutableLayoutObject(), + source.StyleVariant(), + source.IsHorizontal() + ? PhysicalSize{shape_result->SnappedWidth(), source.Size().height} + : PhysicalSize{source.Size().width, shape_result->SnappedWidth()}, + kFragmentText, + source.TextType()), text_(source.text_), start_offset_(start_offset), end_offset_(end_offset), @@ -75,11 +66,9 @@ NGPhysicalTextFragment::NGPhysicalTextFragment( DCHECK_GE(start_offset_, source.StartOffset()); DCHECK_LE(end_offset_, source.EndOffset()); DCHECK(shape_result_ || IsFlowControl()) << ToString(); - DCHECK(!source.rare_data_ || !source.rare_data_->style_); line_orientation_ = source.line_orientation_; - is_anonymous_text_ = source.is_anonymous_text_; - - UpdateSelfInkOverflow(); + is_generated_text_ = source.is_generated_text_; + ink_overflow_computed_ = false; } NGPhysicalTextFragment::NGPhysicalTextFragment(NGTextFragmentBuilder* builder) @@ -91,53 +80,25 @@ NGPhysicalTextFragment::NGPhysicalTextFragment(NGTextFragmentBuilder* builder) DCHECK(shape_result_ || IsFlowControl()) << ToString(); line_orientation_ = static_cast<unsigned>(ToLineOrientation(builder->GetWritingMode())); - - if (UNLIKELY(StyleVariant() == NGStyleVariant::kEllipsis)) { - EnsureRareData()->style_ = std::move(builder->style_); - is_anonymous_text_ = true; - } else { - is_anonymous_text_ = - builder->text_type_ == kGeneratedText || - IsPhysicalTextFragmentAnonymousText(builder->layout_object_); - } - - UpdateSelfInkOverflow(); -} - -NGPhysicalTextFragment::RareData* NGPhysicalTextFragment::EnsureRareData() { - if (!rare_data_) - rare_data_ = std::make_unique<RareData>(); - return rare_data_.get(); -} - -const ComputedStyle& NGPhysicalTextFragment::Style() const { - switch (StyleVariant()) { - case NGStyleVariant::kStandard: - case NGStyleVariant::kFirstLine: - return NGPhysicalFragment::Style(); - case NGStyleVariant::kEllipsis: - DCHECK(rare_data_ && rare_data_->style_); - return *rare_data_->style_; - } - NOTREACHED(); - return NGPhysicalFragment::Style(); + is_generated_text_ = builder->IsGeneratedText(); + ink_overflow_computed_ = false; } // Convert logical cooridnate to local physical coordinate. -NGPhysicalOffsetRect NGPhysicalTextFragment::ConvertToLocal( +PhysicalRect NGPhysicalTextFragment::ConvertToLocal( const LayoutRect& logical_rect) const { switch (LineOrientation()) { case NGLineOrientation::kHorizontal: - return NGPhysicalOffsetRect(logical_rect); + return PhysicalRect(logical_rect); case NGLineOrientation::kClockWiseVertical: - return {{size_.width - logical_rect.MaxY(), logical_rect.X()}, - {logical_rect.Height(), logical_rect.Width()}}; + return {size_.width - logical_rect.MaxY(), logical_rect.X(), + logical_rect.Height(), logical_rect.Width()}; case NGLineOrientation::kCounterClockWiseVertical: - return {{logical_rect.Y(), size_.height - logical_rect.MaxX()}, - {logical_rect.Height(), logical_rect.Width()}}; + return {logical_rect.Y(), size_.height - logical_rect.MaxX(), + logical_rect.Height(), logical_rect.Width()}; } NOTREACHED(); - return NGPhysicalOffsetRect(logical_rect); + return PhysicalRect(logical_rect); } // Compute the inline position from text offset, in logical coordinate relative @@ -190,9 +151,8 @@ NGPhysicalTextFragment::LineLeftAndRightForOffsets(unsigned start_offset, : std::make_pair(start_position, end_position); } -NGPhysicalOffsetRect NGPhysicalTextFragment::LocalRect( - unsigned start_offset, - unsigned end_offset) const { +PhysicalRect NGPhysicalTextFragment::LocalRect(unsigned start_offset, + unsigned end_offset) const { if (start_offset == start_offset_ && end_offset == end_offset_) return LocalRect(); LayoutUnit start_position, end_position; @@ -201,34 +161,38 @@ NGPhysicalOffsetRect NGPhysicalTextFragment::LocalRect( const LayoutUnit inline_size = end_position - start_position; switch (LineOrientation()) { case NGLineOrientation::kHorizontal: - return {{start_position, LayoutUnit()}, {inline_size, Size().height}}; + return {start_position, LayoutUnit(), inline_size, Size().height}; case NGLineOrientation::kClockWiseVertical: - return {{LayoutUnit(), start_position}, {Size().width, inline_size}}; + return {LayoutUnit(), start_position, Size().width, inline_size}; case NGLineOrientation::kCounterClockWiseVertical: - return {{LayoutUnit(), Size().height - end_position}, - {Size().width, inline_size}}; + return {LayoutUnit(), Size().height - end_position, Size().width, + inline_size}; } NOTREACHED(); return {}; } -NGPhysicalOffsetRect NGPhysicalTextFragment::SelfInkOverflow() const { - return UNLIKELY(rare_data_) ? rare_data_->self_ink_overflow_ : LocalRect(); +PhysicalRect NGPhysicalTextFragment::SelfInkOverflow() const { + if (!ink_overflow_computed_) + ComputeSelfInkOverflow(); + return self_ink_overflow_; } -void NGPhysicalTextFragment::ClearSelfInkOverflow() { - if (UNLIKELY(rare_data_)) - rare_data_->self_ink_overflow_ = LocalRect(); +void NGPhysicalTextFragment::ClearSelfInkOverflow() const { + self_ink_overflow_ = LocalRect(); } -void NGPhysicalTextFragment::UpdateSelfInkOverflow() { +void NGPhysicalTextFragment::ComputeSelfInkOverflow() const { + ink_overflow_computed_ = true; + if (UNLIKELY(!shape_result_)) { ClearSelfInkOverflow(); return; } // Glyph bounds is in logical coordinate, origin at the alphabetic baseline. - LayoutRect ink_overflow = EnclosingLayoutRect(shape_result_->Bounds()); + FloatRect text_ink_bounds = Style().GetFont().TextInkBounds(PaintInfo()); + LayoutRect ink_overflow = EnclosingLayoutRect(text_ink_bounds); // Make the origin at the logical top of this fragment. const ComputedStyle& style = Style(); @@ -269,18 +233,18 @@ void NGPhysicalTextFragment::UpdateSelfInkOverflow() { // Uniting the frame rect ensures that non-ink spaces such side bearings, or // even space characters, are included in the visual rect for decorations. - NGPhysicalOffsetRect local_ink_overflow = ConvertToLocal(ink_overflow); - NGPhysicalOffsetRect local_rect = LocalRect(); + PhysicalRect local_ink_overflow = ConvertToLocal(ink_overflow); + PhysicalRect local_rect = LocalRect(); if (local_rect.Contains(local_ink_overflow)) { - ClearSelfInkOverflow(); + self_ink_overflow_ = local_rect; return; } local_ink_overflow.Unite(local_rect); local_ink_overflow.ExpandEdgesToPixelBoundaries(); - EnsureRareData()->self_ink_overflow_ = local_ink_overflow; + self_ink_overflow_ = local_ink_overflow; } -scoped_refptr<const NGPhysicalFragment> NGPhysicalTextFragment::TrimText( +scoped_refptr<const NGPhysicalTextFragment> NGPhysicalTextFragment::TrimText( unsigned new_start_offset, unsigned new_end_offset) const { DCHECK(shape_result_); @@ -294,7 +258,7 @@ scoped_refptr<const NGPhysicalFragment> NGPhysicalTextFragment::TrimText( } unsigned NGPhysicalTextFragment::TextOffsetForPoint( - const NGPhysicalOffset& point) const { + const PhysicalOffset& point) const { const ComputedStyle& style = Style(); const LayoutUnit& point_in_line_direction = style.IsHorizontalWritingMode() ? point.left : point.top; @@ -311,7 +275,7 @@ unsigned NGPhysicalTextFragment::TextOffsetForPoint( DCHECK(IsFlowControl()); // Zero-inline-size objects such as newline always return the start offset. - NGLogicalSize size = Size().ConvertToLogical(style.GetWritingMode()); + LogicalSize size = Size().ConvertToLogical(style.GetWritingMode()); if (!size.inline_size) return StartOffset(); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h index c3f7669da89..c46013f2bc8 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h @@ -17,8 +17,9 @@ namespace blink { -struct NGPhysicalOffsetRect; +struct PhysicalRect; class NGTextFragmentBuilder; +class NGPhysicalTextFragment; enum class AdjustMidCluster; @@ -54,7 +55,7 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { // |text_| holds generated contents instead of |text_content_| in // |NGNodeInlineData|, e.g. hyphen, and ellipsis. // Note: Contents generated by CSS pseudo element, e.g. ::before, ::after, - // are not classified to this. See IsAnonymousText() for them. + // are not classified to this. See IsGeneratedText() for them. kGeneratedText, // When adding new values, make sure the bit size of |sub_type_| is large // enough to store. @@ -63,8 +64,9 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { NGPhysicalTextFragment(NGTextFragmentBuilder*); NGTextType TextType() const { return static_cast<NGTextType>(sub_type_); } - // True if this is a generated text. - bool IsGeneratedText() const { return TextType() == kGeneratedText; } + // Returns true if the text is generated (from, e.g., list marker, + // pseudo-element, ...) instead of from a DOM text node. + bool IsGeneratedText() const { return is_generated_text_; } // True if this is a forced line break. bool IsLineBreak() const { return TextType() == kForcedLineBreak; } // True if this is not for painting; i.e., a forced line break, a tabulation, @@ -72,8 +74,10 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { bool IsFlowControl() const { return IsLineBreak() || TextType() == kFlowControl; } - - const ComputedStyle& Style() const; + // True if this is an ellpisis generated by `text-overflow: ellipsis`. + bool IsEllipsis() const { + return StyleVariant() == NGStyleVariant::kEllipsis; + } unsigned Length() const { return end_offset_ - start_offset_; } StringView Text() const { return StringView(text_, start_offset_, Length()); } @@ -99,18 +103,18 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { // The layout box of text in (start, end) range in local coordinate. // Start and end offsets must be between StartOffset() and EndOffset(). - NGPhysicalOffsetRect LocalRect(unsigned start_offset, - unsigned end_offset) const; + PhysicalRect LocalRect(unsigned start_offset, unsigned end_offset) const; using NGPhysicalFragment::LocalRect; // The visual bounding box that includes glpyh bounding box and CSS // properties, in local coordinates. - NGPhysicalOffsetRect SelfInkOverflow() const; + PhysicalRect SelfInkOverflow() const; // Create a new fragment that has part of the text of this fragment. // All other properties are the same as this fragment. - scoped_refptr<const NGPhysicalFragment> TrimText(unsigned start_offset, - unsigned end_offset) const; + scoped_refptr<const NGPhysicalTextFragment> TrimText( + unsigned start_offset, + unsigned end_offset) const; scoped_refptr<const NGPhysicalFragment> CloneWithoutOffset() const; @@ -119,12 +123,8 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { TextShapeResult()}; } - // Returns true if the text is generated (from, e.g., list marker, - // pseudo-element, ...) instead of from a DOM text node. - bool IsAnonymousText() const { return is_anonymous_text_; } - // Returns the text offset in the fragment placed closest to the given point. - unsigned TextOffsetForPoint(const NGPhysicalOffset&) const; + unsigned TextOffsetForPoint(const PhysicalOffset&) const; UBiDiLevel BidiLevel() const; TextDirection ResolvedDirection() const; @@ -143,23 +143,14 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { unsigned end_offset, scoped_refptr<const ShapeResultView> shape_result); - struct RareData { - USING_FAST_MALLOC(RareData); - - public: - NGPhysicalOffsetRect self_ink_overflow_; - scoped_refptr<const ComputedStyle> style_; // Used only for ellipsis. - }; - RareData* EnsureRareData(); - LayoutUnit InlinePositionForOffset(unsigned offset, LayoutUnit (*round)(float), AdjustMidCluster) const; - NGPhysicalOffsetRect ConvertToLocal(const LayoutRect&) const; + PhysicalRect ConvertToLocal(const LayoutRect&) const; - void UpdateSelfInkOverflow(); - void ClearSelfInkOverflow(); + void ComputeSelfInkOverflow() const; + void ClearSelfInkOverflow() const; // The text of NGInlineNode; i.e., of a parent block. The text for this // fragment is a substring(start_offset_, end_offset_) of this string. @@ -170,7 +161,10 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { const unsigned end_offset_; const scoped_refptr<const ShapeResultView> shape_result_; - std::unique_ptr<RareData> rare_data_; + // Fragments are immutable but allow certain expensive data, specifically ink + // overflow, to be cached as long as it is guaranteed to always recompute to + // the same value. + mutable PhysicalRect self_ink_overflow_; }; template <> diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc index ce4035dd595..1867897f0d7 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc @@ -4,7 +4,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_rect.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" @@ -55,9 +55,7 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRect) { )HTML"); auto text_fragments = CollectTextFragmentsInContainer("container"); ASSERT_EQ(2u, text_fragments.size()); - EXPECT_EQ(NGPhysicalOffsetRect({LayoutUnit(20), LayoutUnit(0)}, - {LayoutUnit(20), LayoutUnit(10)}), - text_fragments[1]->LocalRect(8, 10)); + EXPECT_EQ(PhysicalRect(20, 0, 20, 10), text_fragments[1]->LocalRect(8, 10)); } TEST_F(NGPhysicalTextFragmentTest, LocalRectRTL) { @@ -78,8 +76,7 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRectRTL) { // The 2nd line starts at 12, because the div has a bidi-control. EXPECT_EQ(12u, text_fragments[1]->StartOffset()); // TODO(layout-dev): Investigate whether this is correct. - // EXPECT_EQ(NGPhysicalOffsetRect({LayoutUnit(50), LayoutUnit(0)}, - // {LayoutUnit(20), LayoutUnit(10)}), + // EXPECT_EQ(PhysicalRect(50, 0, 20, 10), // text_fragments[1]->LocalRect(14, 16)); } @@ -97,9 +94,7 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRectVLR) { )HTML"); auto text_fragments = CollectTextFragmentsInContainer("container"); ASSERT_EQ(2u, text_fragments.size()); - EXPECT_EQ(NGPhysicalOffsetRect({LayoutUnit(0), LayoutUnit(20)}, - {LayoutUnit(10), LayoutUnit(20)}), - text_fragments[1]->LocalRect(8, 10)); + EXPECT_EQ(PhysicalRect(0, 20, 10, 20), text_fragments[1]->LocalRect(8, 10)); } TEST_F(NGPhysicalTextFragmentTest, LocalRectVRL) { @@ -116,9 +111,7 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRectVRL) { )HTML"); auto text_fragments = CollectTextFragmentsInContainer("container"); ASSERT_EQ(2u, text_fragments.size()); - EXPECT_EQ(NGPhysicalOffsetRect({LayoutUnit(0), LayoutUnit(20)}, - {LayoutUnit(10), LayoutUnit(20)}), - text_fragments[1]->LocalRect(8, 10)); + EXPECT_EQ(PhysicalRect(0, 20, 10, 20), text_fragments[1]->LocalRect(8, 10)); } TEST_F(NGPhysicalTextFragmentTest, NormalTextIsNotAnonymousText) { @@ -128,7 +121,7 @@ TEST_F(NGPhysicalTextFragmentTest, NormalTextIsNotAnonymousText) { ASSERT_EQ(1u, text_fragments.size()); const NGPhysicalTextFragment& text = *text_fragments[0]; - EXPECT_FALSE(text.IsAnonymousText()); + EXPECT_FALSE(text.IsGeneratedText()); } TEST_F(NGPhysicalTextFragmentTest, FirstLetterIsNotAnonymousText) { @@ -141,8 +134,8 @@ TEST_F(NGPhysicalTextFragmentTest, FirstLetterIsNotAnonymousText) { const NGPhysicalTextFragment& first_letter = *text_fragments[0]; const NGPhysicalTextFragment& remaining_text = *text_fragments[1]; - EXPECT_FALSE(first_letter.IsAnonymousText()); - EXPECT_FALSE(remaining_text.IsAnonymousText()); + EXPECT_FALSE(first_letter.IsGeneratedText()); + EXPECT_FALSE(remaining_text.IsGeneratedText()); } TEST_F(NGPhysicalTextFragmentTest, BeforeAndAfterAreAnonymousText) { @@ -156,9 +149,9 @@ TEST_F(NGPhysicalTextFragmentTest, BeforeAndAfterAreAnonymousText) { const NGPhysicalTextFragment& before = *text_fragments[0]; const NGPhysicalTextFragment& text = *text_fragments[1]; const NGPhysicalTextFragment& after = *text_fragments[2]; - EXPECT_TRUE(before.IsAnonymousText()); - EXPECT_FALSE(text.IsAnonymousText()); - EXPECT_TRUE(after.IsAnonymousText()); + EXPECT_TRUE(before.IsGeneratedText()); + EXPECT_FALSE(text.IsGeneratedText()); + EXPECT_TRUE(after.IsGeneratedText()); } TEST_F(NGPhysicalTextFragmentTest, Ellipsis) { @@ -181,15 +174,13 @@ TEST_F(NGPhysicalTextFragmentTest, Ellipsis) { const NGPhysicalTextFragment& ellipsis = *text_fragments[1]; EXPECT_EQ(NGPhysicalTextFragment::kNormalText, abcdef.TextType()); EXPECT_FALSE(abcdef.IsGeneratedText()); - EXPECT_FALSE(abcdef.IsAnonymousText()); EXPECT_EQ(u8"abc", GetText(abcdef)); EXPECT_EQ(NGPhysicalTextFragment::kGeneratedText, ellipsis.TextType()); EXPECT_TRUE(ellipsis.IsGeneratedText()); - EXPECT_TRUE(ellipsis.IsAnonymousText()); EXPECT_EQ(u8"\u2026", GetText(ellipsis)); } -TEST_F(NGPhysicalTextFragmentTest, ListMarkerIsAnonymousText) { +TEST_F(NGPhysicalTextFragmentTest, ListMarkerIsGeneratedText) { SetBodyInnerHTML( "<ol style='list-style-position:inside'>" "<li id=list>text</li>" @@ -200,8 +191,8 @@ TEST_F(NGPhysicalTextFragmentTest, ListMarkerIsAnonymousText) { const NGPhysicalTextFragment& marker = *text_fragments[0]; const NGPhysicalTextFragment& text = *text_fragments[1]; - EXPECT_TRUE(marker.IsAnonymousText()); - EXPECT_FALSE(text.IsAnonymousText()); + EXPECT_TRUE(marker.IsGeneratedText()); + EXPECT_FALSE(text.IsGeneratedText()); } TEST_F(NGPhysicalTextFragmentTest, SoftHyphen) { @@ -222,17 +213,14 @@ TEST_F(NGPhysicalTextFragmentTest, SoftHyphen) { const NGPhysicalTextFragment& shy = *text_fragments[1]; const NGPhysicalTextFragment& def = *text_fragments[2]; EXPECT_EQ(NGPhysicalTextFragment::kNormalText, abc.TextType()); - EXPECT_FALSE(abc.IsGeneratedText()); // Note: ShapeResult::RunInfo.width_ == 0 for U+00AD EXPECT_EQ(u8"abc\u00AD", GetText(abc)); EXPECT_EQ(NGPhysicalTextFragment::kGeneratedText, shy.TextType()); - EXPECT_TRUE(shy.IsGeneratedText()); // Note: |ComputedStyle::HypenString()| returns "-" or U+2010 based on // glyph availability. if (GetText(shy) != "-") EXPECT_EQ(u8"\u2010", GetText(shy)); EXPECT_EQ(NGPhysicalTextFragment::kNormalText, def.TextType()); - EXPECT_FALSE(def.IsGeneratedText()); } TEST_F(NGPhysicalTextFragmentTest, QuotationMarksAreAnonymousText) { @@ -244,9 +232,9 @@ TEST_F(NGPhysicalTextFragmentTest, QuotationMarksAreAnonymousText) { const NGPhysicalTextFragment& open_quote = *text_fragments[0]; const NGPhysicalTextFragment& text = *text_fragments[1]; const NGPhysicalTextFragment& closed_quote = *text_fragments[2]; - EXPECT_TRUE(open_quote.IsAnonymousText()); - EXPECT_FALSE(text.IsAnonymousText()); - EXPECT_TRUE(closed_quote.IsAnonymousText()); + EXPECT_TRUE(open_quote.IsGeneratedText()); + EXPECT_FALSE(text.IsGeneratedText()); + EXPECT_TRUE(closed_quote.IsGeneratedText()); } TEST_F(NGPhysicalTextFragmentTest, TextOffsetForPointForTabulation) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc index 07ee69f28b1..65b78d5bd1b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h" +#include "third_party/blink/renderer/core/layout/layout_text_fragment.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h" @@ -58,6 +59,21 @@ void NGTextFragmentBuilder::SetText( end_effect_ = NGTextEndEffect::kNone; } +bool NGTextFragmentBuilder::IsGeneratedText() const { + if (UNLIKELY(style_variant_ == NGStyleVariant::kEllipsis || + text_type_ == NGPhysicalTextFragment::kGeneratedText)) + return true; + + DCHECK(layout_object_); + if (const auto* layout_text_fragment = + ToLayoutTextFragmentOrNull(layout_object_)) { + return !layout_text_fragment->AssociatedTextNode(); + } + + const Node* node = layout_object_->GetNode(); + return !node || node->IsPseudoElement(); +} + scoped_refptr<const NGPhysicalTextFragment> NGTextFragmentBuilder::ToTextFragment() { scoped_refptr<const NGPhysicalTextFragment> fragment = diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h index bf000858f53..b219736b393 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h @@ -5,7 +5,7 @@ #ifndef NGTextFragmentBuilder_h #define NGTextFragmentBuilder_h -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h" @@ -43,6 +43,10 @@ class CORE_EXPORT NGTextFragmentBuilder final : public NGFragmentBuilder { scoped_refptr<const NGPhysicalTextFragment> ToTextFragment(); private: + // Returns true if the text is generated (from, e.g., list marker, + // pseudo-element, ...) instead of from a DOM text node. + bool IsGeneratedText() const; + NGInlineNode inline_node_; String text_; unsigned item_index_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_box_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_box_utils.cc index fb5a3e5c346..e1c609da844 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_box_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_box_utils.cc @@ -138,7 +138,7 @@ NGStaticPosition LayoutBoxUtils::ComputeStaticPositionFromLegacy( .ToConstraintSpace(); NGBoxStrut border_scrollbar = ComputeBorders(non_anonymous_space, container_node) + - container_node.GetScrollbarSizes(); + ComputeScrollbars(non_anonymous_space, container_node); // Now make it relative to the left or top border edge of the containing // block. @@ -170,10 +170,10 @@ NGStaticPosition LayoutBoxUtils::ComputeStaticPositionFromLegacy( ? container_border_box_logical_height - static_block : static_block; - NGPhysicalOffset static_location = + PhysicalOffset static_location = container_style->IsHorizontalWritingMode() - ? NGPhysicalOffset(inline_left_or_top, block_top_or_left) - : NGPhysicalOffset(block_top_or_left, inline_left_or_top); + ? PhysicalOffset(inline_left_or_top, block_top_or_left) + : PhysicalOffset(block_top_or_left, inline_left_or_top); return NGStaticPosition::Create(writing_mode, parent_style->Direction(), static_location); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc index 5f64e2c3454..ab090971bf4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc @@ -46,8 +46,10 @@ void LayoutNGBlockFlow::UpdateBlockLayout(bool relayout_children) { NGBlockNode(this).Layout(constraint_space); for (const NGOutOfFlowPositionedDescendant& descendant : - result->OutOfFlowPositionedDescendants()) - descendant.node.UseOldOutOfFlowPositioning(); + result->PhysicalFragment().OutOfFlowPositionedDescendants()) + descendant.node.UseLegacyOutOfFlowPositioning(); + + UpdateMargins(constraint_space); } void LayoutNGBlockFlow::UpdateOutOfFlowBlockLayout() { @@ -78,7 +80,7 @@ void LayoutNGBlockFlow::UpdateOutOfFlowBlockLayout() { // borders and scrollbars to compensate. NGBoxStrut border_scrollbar = ComputeBorders(constraint_space, container_node) + - NGBlockNode(container).GetScrollbarSizes(); + ComputeScrollbars(constraint_space, container_node); // Calculate the border-box size of the object that's the containing block of // this out-of-flow positioned descendant. Note that this is not to be used as @@ -104,27 +106,26 @@ void LayoutNGBlockFlow::UpdateOutOfFlowBlockLayout() { container_border_box_logical_height = container->LogicalHeight(); } - container_builder.SetInlineSize(container_border_box_logical_width); - container_builder.SetBlockSize(container_border_box_logical_height); - container_builder.SetBorders( - ComputeBorders(constraint_space, container_node)); - container_builder.SetPadding( - ComputePadding(constraint_space, *container_style)); + NGFragmentGeometry fragment_geometry; + fragment_geometry.border_box_size = {container_border_box_logical_width, + container_border_box_logical_height}; + fragment_geometry.border = ComputeBorders(constraint_space, container_node); + fragment_geometry.padding = + ComputePadding(constraint_space, *container_style); + container_builder.SetInitialFragmentGeometry(fragment_geometry); NGStaticPosition static_position = LayoutBoxUtils::ComputeStaticPositionFromLegacy(*this); // Set correct container for inline containing blocks. container_builder.AddOutOfFlowLegacyCandidate( - NGBlockNode(this), static_position, - css_container->IsBox() ? nullptr : css_container); + NGBlockNode(this), static_position, ToLayoutInlineOrNull(css_container)); - base::Optional<NGLogicalSize> initial_containing_block_fixed_size; + base::Optional<LogicalSize> initial_containing_block_fixed_size; if (container->IsLayoutView() && !GetDocument().Printing()) { if (LocalFrameView* frame_view = ToLayoutView(container)->GetFrameView()) { IntSize size = frame_view->LayoutViewport()->ExcludeScrollbars(frame_view->Size()); - NGPhysicalSize physical_size = - NGPhysicalSize(LayoutUnit(size.Width()), LayoutUnit(size.Height())); + PhysicalSize physical_size(size); initial_containing_block_fixed_size = physical_size.ConvertToLogical(container->Style()->GetWritingMode()); } @@ -142,21 +143,21 @@ void LayoutNGBlockFlow::UpdateOutOfFlowBlockLayout() { container_builder.ToBoxFragment(); // These are the unpositioned OOF descendants of the current OOF block. for (const NGOutOfFlowPositionedDescendant& descendant : - result->OutOfFlowPositionedDescendants()) - descendant.node.UseOldOutOfFlowPositioning(); + result->PhysicalFragment().OutOfFlowPositionedDescendants()) + descendant.node.UseLegacyOutOfFlowPositioning(); - const auto* fragment = To<NGPhysicalBoxFragment>(result->PhysicalFragment()); - DCHECK_GT(fragment->Children().size(), 0u); + const auto& fragment = result->PhysicalFragment(); + DCHECK_GT(fragment.Children().size(), 0u); // Copy sizes of all child fragments to Legacy. // There could be multiple fragments, when this node has descendants whose // container is this node's container. // Example: fixed descendant of fixed element. - for (auto& child : fragment->Children()) { + for (auto& child : fragment.Children()) { const NGPhysicalFragment* child_fragment = child.get(); DCHECK(child_fragment->GetLayoutObject()->IsBox()); LayoutBox* child_legacy_box = - ToLayoutBox(child_fragment->GetLayoutObject()); - NGPhysicalOffset child_offset = child.Offset(); + ToLayoutBox(child_fragment->GetMutableLayoutObject()); + PhysicalOffset child_offset = child.Offset(); if (container_style->IsFlippedBlocksWritingMode()) { child_legacy_box->SetX(container_border_box_logical_height - child_offset.left - child_fragment->Size().width); @@ -165,8 +166,30 @@ void LayoutNGBlockFlow::UpdateOutOfFlowBlockLayout() { } child_legacy_box->SetY(child_offset.top); } - DCHECK_EQ(fragment->Children()[0]->GetLayoutObject(), this); + DCHECK_EQ(fragment.Children()[0]->GetLayoutObject(), this); SetIsLegacyInitiatedOutOfFlowLayout(true); } +void LayoutNGBlockFlow::UpdateMargins(const NGConstraintSpace& space) { + const LayoutBlock* containing_block = 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 = StyleRef(); + const ComputedStyle& cb_style = containing_block->StyleRef(); + const auto writing_mode = cb_style.GetWritingMode(); + const auto direction = cb_style.Direction(); + LayoutUnit percentage_resolution_size = + space.PercentageResolutionInlineSizeForParentWritingMode(); + NGBoxStrut margins = ComputePhysicalMargins(style, percentage_resolution_size) + .ConvertToLogical(writing_mode, direction); + ResolveInlineMargins(style, cb_style, space.AvailableSize().inline_size, + LogicalWidth(), &margins); + SetMargin(margins.ConvertToPhysical(writing_mode, direction)); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h index f36533ea284..07792ae7e14 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h @@ -26,6 +26,7 @@ class CORE_EXPORT LayoutNGBlockFlow : public LayoutNGMixin<LayoutBlockFlow> { private: void UpdateOutOfFlowBlockLayout(); + void UpdateMargins(const NGConstraintSpace&); }; DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGBlockFlow, IsLayoutNGBlockFlow()); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.cc index 6384487adc7..5b1bfac5755 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.cc @@ -29,8 +29,8 @@ void LayoutNGFlexibleBox::UpdateBlockLayout(bool relayout_children) { NGBlockNode(this).Layout(constraint_space); for (NGOutOfFlowPositionedDescendant descendant : - result->OutOfFlowPositionedDescendants()) - descendant.node.UseOldOutOfFlowPositioning(); + result->PhysicalFragment().OutOfFlowPositionedDescendants()) + descendant.node.UseLegacyOutOfFlowPositioning(); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc index 45ae19674b9..cd9887971db 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc @@ -46,9 +46,7 @@ void LayoutNGMixin<Base>::StyleDidChange(StyleDifference diff, const ComputedStyle* old_style) { Base::StyleDidChange(diff, old_style); - const ComputedStyle& new_style = Base::StyleRef(); - if (old_style && Base::ChildrenInline() && - new_style.GetUnicodeBidi() != old_style->GetUnicodeBidi()) { + if (diff.NeedsCollectInlines()) { Base::SetNeedsCollectInlines(); } } @@ -85,7 +83,7 @@ const NGPhysicalBoxFragment* LayoutNGMixin<Base>::CurrentFragment() const { if (!cached_layout_result) return nullptr; - return To<NGPhysicalBoxFragment>(cached_layout_result->PhysicalFragment()); + return &To<NGPhysicalBoxFragment>(cached_layout_result->PhysicalFragment()); } template <typename Base> @@ -154,7 +152,7 @@ void LayoutNGMixin<Base>::AddScrollingOverflowFromChildren() { Base::StyleRef().Direction()); } - NGPhysicalOffsetRect children_overflow; + PhysicalRect children_overflow; // Only add overflow for fragments NG has not reflected into Legacy. // These fragments are: @@ -163,8 +161,12 @@ void LayoutNGMixin<Base>::AddScrollingOverflowFromChildren() { // 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()) { + LayoutUnit border_inline_start = + LayoutUnit(Base::StyleRef().BorderStartWidth()); + LayoutUnit border_block_start = + LayoutUnit(Base::StyleRef().BorderBeforeWidth()); for (const auto& child : physical_fragment->Children()) { - NGPhysicalOffsetRect child_scrollable_overflow; + PhysicalRect child_scrollable_overflow; if (child->IsOutOfFlowPositioned()) { child_scrollable_overflow = child->ScrollableOverflowForPropagation(this); @@ -179,7 +181,18 @@ void LayoutNGMixin<Base>::AddScrollingOverflowFromChildren() { continue; } child_scrollable_overflow.offset += child.Offset(); - children_overflow.Unite(child_scrollable_overflow); + + // Do not add overflow if fragment is not reachable by scrolling. + WritingMode writing_mode = Base::StyleRef().GetWritingMode(); + LogicalOffset child_logical_end = + child_scrollable_overflow.offset.ConvertToLogical( + writing_mode, Base::StyleRef().Direction(), + physical_fragment->Size(), child_scrollable_overflow.size) + + child_scrollable_overflow.size.ConvertToLogical(writing_mode); + + if (child_logical_end.inline_offset > border_inline_start && + child_logical_end.block_offset > border_block_start) + children_overflow.Unite(child_scrollable_overflow); } } @@ -191,17 +204,27 @@ void LayoutNGMixin<Base>::AddScrollingOverflowFromChildren() { template <typename Base> void LayoutNGMixin<Base>::AddOutlineRects( - Vector<LayoutRect>& rects, - const LayoutPoint& additional_offset, + Vector<PhysicalRect>& rects, + const PhysicalOffset& additional_offset, NGOutlineType include_block_overflows) const { if (PaintFragment()) { - PaintFragment()->AddSelfOutlineRect(&rects, additional_offset, - include_block_overflows); + PaintFragment()->AddSelfOutlineRects(&rects, additional_offset, + include_block_overflows); } else { Base::AddOutlineRects(rects, additional_offset, include_block_overflows); } } +template <typename Base> +bool LayoutNGMixin<Base>::PaintedOutputOfObjectHasNoEffectRegardlessOfSize() + const { + // LayoutNGMixin is in charge of paint invalidation of the first line. + if (PaintFragment()) + return false; + + return Base::PaintedOutputOfObjectHasNoEffectRegardlessOfSize(); +} + // Retrieve NGBaseline from the current fragment. template <typename Base> base::Optional<LayoutUnit> LayoutNGMixin<Base>::FragmentBaseline( @@ -249,39 +272,18 @@ void LayoutNGMixin<Base>::SetPaintFragment( scoped_refptr<NGPaintFragment>* current = NGPaintFragment::Find(&paint_fragment_, break_token); DCHECK(current); - const NGPaintFragment* old = current->get(); if (fragment) { *current = NGPaintFragment::Create(std::move(fragment), break_token, std::move(*current)); - } else { + // |NGPaintFragment::Create()| calls |SlowSetPaintingLayerNeedsRepaint()|. + } else if (*current) { + DCHECK_EQ(this, (*current)->GetLayoutObject()); *current = nullptr; - } - - if (old && old != current->get()) { - // Painting layer needs repaint when a DisplayItemClient is destroyed. - // TODO(kojii): We need this here for now, but this should be handled - // differently for better efficiency, in pre-paint tree walk to walk - // fragment tree, or before layout. crbug.com/941228 ObjectPaintInvalidator(*this).SlowSetPaintingLayerNeedsRepaint(); } } 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 (const NGPaintFragment* paint_fragment = PaintFragment()) NGBoxFragmentPainter(*paint_fragment).Paint(paint_info); @@ -340,8 +342,10 @@ PositionWithAffinity LayoutNGMixin<Base>::PositionForPoint( if (!PaintFragment()) return Base::CreatePositionWithAffinity(0); + // Flip because |point| is in flipped physical coordinates while + // NGPaintFragment::PositionForPoint() requires pure physical coordinates. const PositionWithAffinity ng_position = - PaintFragment()->PositionForPoint(NGPhysicalOffset(point)); + PaintFragment()->PositionForPoint(Base::FlipForWritingMode(point)); if (ng_position.IsNotNull()) return ng_position; return Base::CreatePositionWithAffinity(0); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h index 9333fb1cc50..7f45d582c85 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h @@ -41,8 +41,6 @@ class LayoutNGMixin : public Base { LayoutUnit FirstLineBoxBaseline() const final; LayoutUnit InlineBlockBaseline(LineDirectionMode) const final; - void InvalidateDisplayItemClients(PaintInvalidationReason) const final; - void Paint(const PaintInfo&) const final; bool NodeAtPoint(HitTestResult&, @@ -50,9 +48,17 @@ class LayoutNGMixin : public Base { const LayoutPoint& accumulated_offset, HitTestAction) final; - PositionWithAffinity PositionForPoint(const LayoutPoint&) const final; - - NGPaintFragment* PaintFragment() const final { return paint_fragment_.get(); } + PositionWithAffinity PositionForPoint(const LayoutPoint&) const override; + + const NGPaintFragment* PaintFragment() const final { + // TODO(layout-dev) crbug.com/963103 + // Safer option here is to return nullptr only if + // Lifecycle > DocumentLifecycle::kAfterPerformLayout, but this breaks + // some layout tests. + if (Base::NeedsLayout()) + return nullptr; + return paint_fragment_.get(); + } void SetPaintFragment(const NGBlockBreakToken*, scoped_refptr<const NGPhysicalFragment>) final; @@ -67,10 +73,12 @@ class LayoutNGMixin : public Base { void AddLayoutOverflowFromChildren() final; - void AddOutlineRects(Vector<LayoutRect>&, - const LayoutPoint& additional_offset, + void AddOutlineRects(Vector<PhysicalRect>&, + const PhysicalOffset& additional_offset, NGOutlineType) const final; + bool PaintedOutputOfObjectHasNoEffectRegardlessOfSize() const final; + const NGPhysicalBoxFragment* CurrentFragment() const final; base::Optional<LayoutUnit> FragmentBaseline(NGBaselineAlgorithmType) const; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_caption.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_caption.cc index 6df5ff54c1b..d6756cc09ba 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_caption.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_caption.cc @@ -37,8 +37,8 @@ void LayoutNGTableCaption::CalculateAndSetMargins( LayoutUnit caption_inline_size_in_cb_writing_mode = box_fragment.InlineSize(); LayoutUnit available_inline_size_in_cb_writing_mode = - ToNGPhysicalSize(constraint_space.AvailableSize(), - constraint_space.GetWritingMode()) + ToPhysicalSize(constraint_space.AvailableSize(), + constraint_space.GetWritingMode()) .ConvertToLogical(containing_block_style.GetWritingMode()) .inline_size; @@ -62,7 +62,7 @@ void LayoutNGTableCaption::UpdateBlockLayout(bool relayout_children) { scoped_refptr<const NGLayoutResult> result = NGBlockNode(this).Layout(constraint_space); - CalculateAndSetMargins(constraint_space, *result->PhysicalFragment()); + CalculateAndSetMargins(constraint_space, result->PhysicalFragment()); // Tell legacy layout there were abspos descendents we couldn't place. We know // we have to pass up to legacy here because this method is legacy's entry @@ -70,15 +70,15 @@ void LayoutNGTableCaption::UpdateBlockLayout(bool relayout_children) { // UpdateBlockLayout, it would have packaged this LayoutObject into // NGBlockNode and called Layout on that. for (NGOutOfFlowPositionedDescendant descendant : - result->OutOfFlowPositionedDescendants()) - descendant.node.UseOldOutOfFlowPositioning(); + result->PhysicalFragment().OutOfFlowPositionedDescendants()) + descendant.node.UseLegacyOutOfFlowPositioning(); // The parent table sometimes changes the caption's position after laying it // out. So there's no point in setting the fragment's offset here; // NGBoxFragmentPainter::Paint will have to handle it until table layout is // implemented in NG, in which case that algorithm will set each child's // offsets. See https://crbug.com/788590 for more info. - DCHECK(!result->PhysicalFragment()->IsPlacedByLayoutNG()) + DCHECK(!result->PhysicalFragment().IsPlacedByLayoutNG()) << "Only a table should be placing table caption fragments and the ng " "table algorithm doesn't exist yet!"; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_cell.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_cell.cc index b1e68657111..d38d1a2bc23 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_cell.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_cell.cc @@ -29,8 +29,8 @@ void LayoutNGTableCell::UpdateBlockLayout(bool relayout_children) { NGBlockNode(this).Layout(constraint_space); for (NGOutOfFlowPositionedDescendant descendant : - result->OutOfFlowPositionedDescendants()) - descendant.node.UseOldOutOfFlowPositioning(); + result->PhysicalFragment().OutOfFlowPositionedDescendants()) + descendant.node.UseLegacyOutOfFlowPositioning(); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.cc new file mode 100644 index 00000000000..6938996dcdf --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.cc @@ -0,0 +1,32 @@ +// 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/list/layout_ng_inside_list_marker.h" + +#include "third_party/blink/renderer/core/layout/layout_text.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" + +namespace blink { + +LayoutNGInsideListMarker::LayoutNGInsideListMarker(Element* element) + : LayoutInline(element) {} + +LayoutNGInsideListMarker* LayoutNGInsideListMarker::CreateAnonymous( + Document* document) { + LayoutNGInsideListMarker* object = new LayoutNGInsideListMarker(nullptr); + object->SetDocumentForAnonymous(document); + return object; +} + +bool LayoutNGInsideListMarker::IsOfType(LayoutObjectType type) const { + return type == kLayoutObjectNGInsideListMarker || + LayoutInline::IsOfType(type); +} + +PositionWithAffinity LayoutNGInsideListMarker::PositionForPoint( + const LayoutPoint&) const { + return CreatePositionWithAffinity(0); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h new file mode 100644 index 00000000000..9197de75027 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_INSIDE_LIST_MARKER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_INSIDE_LIST_MARKER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/layout_inline.h" + +namespace blink { + +class Document; + +// A LayoutObject subclass for inside-positioned list markers in LayoutNG. +class CORE_EXPORT LayoutNGInsideListMarker final : public LayoutInline { + public: + explicit LayoutNGInsideListMarker(Element*); + static LayoutNGInsideListMarker* CreateAnonymous(Document*); + + const char* GetName() const override { return "LayoutNGInsideListMarker"; } + +#if DCHECK_IS_ON() + void AddChild(LayoutObject* new_child, LayoutObject* before_child) override { + // List marker should have at most one child. + DCHECK(!FirstChild()); + LayoutInline::AddChild(new_child, before_child); + } +#endif + + private: + bool IsOfType(LayoutObjectType) const override; + PositionWithAffinity PositionForPoint(const LayoutPoint&) const override; +}; + +DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGInsideListMarker, + IsLayoutNGInsideListMarker()); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_INSIDE_LIST_MARKER_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc index 1f89a1e9042..7a6416b5e15 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc @@ -8,6 +8,7 @@ #include "third_party/blink/renderer/core/layout/layout_inline.h" #include "third_party/blink/renderer/core/layout/layout_list_marker.h" #include "third_party/blink/renderer/core/layout/list_marker_text.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h" #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" @@ -115,7 +116,7 @@ void LayoutNGListItem::UpdateMarkerText() { void LayoutNGListItem::UpdateMarker() { const ComputedStyle& style = StyleRef(); - if (style.ListStyleType() == EListStyleType::kNone) { + if (style.ListStyleType() == EListStyleType::kNone && !IsMarkerImage()) { DestroyMarker(); marker_type_ = kStatic; is_marker_text_updated_ = true; @@ -128,7 +129,7 @@ void LayoutNGListItem::UpdateMarker() { if (marker_ && !marker_->IsLayoutInline()) DestroyMarker(); if (!marker_) - marker_ = LayoutInline::CreateAnonymous(&GetDocument()); + marker_ = LayoutNGInsideListMarker::CreateAnonymous(&GetDocument()); marker_style = ComputedStyle::CreateAnonymousStyleWithDisplay( style, EDisplay::kInline); auto margins = @@ -162,6 +163,21 @@ void LayoutNGListItem::UpdateMarker() { } } +LayoutNGListItem* LayoutNGListItem::FromMarker(const LayoutObject& marker) { + DCHECK(marker.IsLayoutNGListMarkerIncludingInside()); + for (LayoutObject* parent = marker.Parent(); parent; + parent = parent->Parent()) { + if (parent->IsLayoutNGListItem()) { + DCHECK(ToLayoutNGListItem(parent)->Marker() == &marker); + return ToLayoutNGListItem(parent); + } + // These DCHECKs are not critical but to ensure we cover all cases we know. + DCHECK(parent->IsAnonymous()); + DCHECK(parent->IsLayoutBlockFlow() || parent->IsLayoutFlowThread()); + } + return nullptr; +} + int LayoutNGListItem::Value() const { DCHECK(GetNode()); return ordinal_.Value(*GetNode()); @@ -170,6 +186,12 @@ int LayoutNGListItem::Value() const { LayoutNGListItem::MarkerType LayoutNGListItem::MarkerText( StringBuilder* text, MarkerTextFormat format) const { + if (IsMarkerImage()) { + if (format == kWithSuffix) + text->Append(' '); + return kStatic; + } + const ComputedStyle& style = StyleRef(); switch (style.ListStyleType()) { case EListStyleType::kNone: @@ -259,10 +281,20 @@ String LayoutNGListItem::MarkerTextWithoutSuffix() const { return text.ToString(); } +String LayoutNGListItem::TextAlternative(const LayoutObject& marker) { + // For accessibility, return the marker string in the logical order even in + // RTL, reflecting speech order. + if (LayoutNGListItem* list_item = FromMarker(marker)) + return list_item->MarkerTextWithSuffix(); + return g_empty_string; +} + void LayoutNGListItem::UpdateMarkerContentIfNeeded() { DCHECK(marker_); LayoutObject* child = marker_->SlowFirstChild(); + // There should be at most one child. + DCHECK(!child || !child->SlowFirstChild()); if (IsMarkerImage()) { StyleImage* list_style_image = StyleRef().ListStyleImage(); if (child) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h index 204a7ef83a8..c56eaa614e6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h @@ -22,6 +22,9 @@ class CORE_EXPORT LayoutNGListItem final : public LayoutNGBlockFlow { String MarkerTextWithSuffix() const; String MarkerTextWithoutSuffix() const; + // Marker text with suffix, e.g. "1. ", for use in accessibility. + static String TextAlternative(const LayoutObject& marker); + LayoutObject* Marker() const { return marker_; } bool IsMarkerImage() const { return StyleRef().ListStyleImage() && @@ -40,6 +43,9 @@ class CORE_EXPORT LayoutNGListItem final : public LayoutNGBlockFlow { LayoutObject* SymbolMarkerLayoutText() const; static const LayoutObject* FindSymbolMarkerLayoutText(const LayoutObject*); + // Find the LayoutNGListItem from a marker. + static LayoutNGListItem* FromMarker(const LayoutObject& marker); + const char* GetName() const override { return "LayoutNGListItem"; } private: diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item_test.cc new file mode 100644 index 00000000000..2c12d7080b3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item_test.cc @@ -0,0 +1,50 @@ +// 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_base_layout_algorithm_test.h" + +#include "third_party/blink/renderer/core/dom/dom_token_list.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" + +namespace blink { + +class LayoutNGListItemTest : public NGLayoutTest {}; + +namespace { + +TEST_F(LayoutNGListItemTest, InsideWithFirstLine) { + SetBodyInnerHTML(R"HTML( + <!DOCTYPE html> + <style> + li { + list-style-position: inside; + } + .after::first-line { + background: yellow; + } + </style> + <div id=container> + <ul> + <li id=item>test</li> + </ul> + </div> + )HTML"); + + Element* container = GetElementById("container"); + container->classList().Add("after"); + GetDocument().UpdateStyleAndLayoutTree(); + + // The list-item should have a marker. + LayoutNGListItem* list_item = + ToLayoutNGListItem(GetLayoutObjectByElementId("item")); + LayoutObject* marker = list_item->Marker(); + EXPECT_TRUE(marker); + // The marker should have only 1 child. + LayoutObject* marker_child = marker->SlowFirstChild(); + EXPECT_TRUE(marker_child); + EXPECT_FALSE(marker_child->NextSibling()); +} + +} // namespace +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc index a7b13fbcd69..bf9f4090fc3 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc @@ -23,54 +23,21 @@ bool LayoutNGListMarker::IsOfType(LayoutObjectType type) const { LayoutNGMixin<LayoutBlockFlow>::IsOfType(type); } -bool LayoutNGListMarker::IsListMarkerWrapperForBlockContent( - const LayoutObject& object) { - const auto* block_flow = DynamicTo<LayoutBlockFlow>(object); - if (!object.IsAnonymous() || !block_flow) - return false; - if (const LayoutObject* child = block_flow->FirstChild()) { - return child->IsLayoutNGListMarker() && - // The anonymous box should not have other children. - // e.g., <li>text<div>block</div></li> - // In this case, inline layout can handle the list marker. - !child->NextSibling(); - } - return false; -} - -// The LayoutNGListItem this marker belongs to. -LayoutNGListItem* LayoutNGListMarker::ListItem() const { - for (LayoutObject* parent = Parent(); parent; parent = parent->Parent()) { - if (parent->IsLayoutNGListItem()) { - DCHECK(ToLayoutNGListItem(parent)->Marker() == this); - return ToLayoutNGListItem(parent); - } - // These DCHECKs are not critical but to ensure we cover all cases we know. - DCHECK(parent->IsAnonymous()); - DCHECK(parent->IsLayoutBlockFlow() || parent->IsLayoutFlowThread()); - } - return nullptr; -} - void LayoutNGListMarker::WillCollectInlines() { - if (LayoutNGListItem* list_item = ListItem()) + if (LayoutNGListItem* list_item = LayoutNGListItem::FromMarker(*this)) list_item->UpdateMarkerTextIfNeeded(); } bool LayoutNGListMarker::IsContentImage() const { - return ListItem()->IsMarkerImage(); + if (LayoutNGListItem* list_item = LayoutNGListItem::FromMarker(*this)) + return list_item->IsMarkerImage(); + return false; } LayoutObject* LayoutNGListMarker::SymbolMarkerLayoutText() const { - return ListItem()->SymbolMarkerLayoutText(); -} - -String LayoutNGListMarker::TextAlternative() const { - // Compute from the list item, in the logical order even in RTL, reflecting - // speech order. - if (LayoutNGListItem* list_item = ListItem()) - return list_item->MarkerTextWithSuffix(); - return g_empty_string; + if (LayoutNGListItem* list_item = LayoutNGListItem::FromMarker(*this)) + return list_item->SymbolMarkerLayoutText(); + return nullptr; } bool LayoutNGListMarker::NeedsOccupyWholeLine() const { @@ -86,4 +53,9 @@ bool LayoutNGListMarker::NeedsOccupyWholeLine() const { return false; } +PositionWithAffinity LayoutNGListMarker::PositionForPoint( + const LayoutPoint&) const { + return CreatePositionWithAffinity(0); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h index 469de1bebe9..74eb29ed096 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h @@ -12,40 +12,27 @@ namespace blink { class Document; -class LayoutNGListItem; -// A LayoutObject subclass for list markers in LayoutNG. +// A LayoutObject subclass for outside-positioned list markers in LayoutNG. class CORE_EXPORT LayoutNGListMarker final : public LayoutNGMixin<LayoutBlockFlow> { public: explicit LayoutNGListMarker(Element*); static LayoutNGListMarker* CreateAnonymous(Document*); - // True if the LayoutObject is a list marker wrapper for block content. - // - // Because a list marker in LayoutNG is an inline block, and because CSS - // defines all children of a box must be either inline level or block level, - // when the content of an list item is block level, the list marker is wrapped - // in an anonymous block box. This function determines such an anonymous box. - static bool IsListMarkerWrapperForBlockContent(const LayoutObject&); - void WillCollectInlines() override; bool IsContentImage() const; LayoutObject* SymbolMarkerLayoutText() const; - // Marker text with suffix, e.g. "1. ", for use in accessibility. - String TextAlternative() const; - const char* GetName() const override { return "LayoutNGListMarker"; } - LayoutNGListItem* ListItem() const; - bool NeedsOccupyWholeLine() const; private: bool IsOfType(LayoutObjectType) const override; + PositionWithAffinity PositionForPoint(const LayoutPoint&) const override; }; DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGListMarker, IsLayoutNGListMarker()); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.cc index dd01187cac8..0231815b925 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.cc @@ -47,7 +47,7 @@ scoped_refptr<const NGLayoutResult> NGUnpositionedListMarker::Layout( scoped_refptr<const NGLayoutResult> marker_layout_result = marker_node.LayoutAtomicInline(parent_space, parent_style, baseline_type, parent_space.UseFirstLineStyle()); - DCHECK(marker_layout_result && marker_layout_result->PhysicalFragment()); + DCHECK(marker_layout_result); return marker_layout_result; } @@ -55,7 +55,7 @@ bool NGUnpositionedListMarker::AddToBox( const NGConstraintSpace& space, FontBaseline baseline_type, const NGPhysicalFragment& content, - NGLogicalOffset* content_offset, + LogicalOffset* content_offset, NGBoxFragmentBuilder* container_builder, const NGBoxStrut& border_scrollbar_padding) const { // Baselines from two different writing-mode cannot be aligned. @@ -90,16 +90,15 @@ bool NGUnpositionedListMarker::AddToBox( // Layout the list marker. scoped_refptr<const NGLayoutResult> marker_layout_result = Layout(space, container_builder->Style(), baseline_type); - DCHECK(marker_layout_result && marker_layout_result->PhysicalFragment()); + DCHECK(marker_layout_result); const NGPhysicalBoxFragment& marker_physical_fragment = - To<NGPhysicalBoxFragment>(*marker_layout_result->PhysicalFragment()); + To<NGPhysicalBoxFragment>(marker_layout_result->PhysicalFragment()); // Compute the inline offset of the marker. NGBoxFragment marker_fragment(space.GetWritingMode(), space.Direction(), marker_physical_fragment); - NGLogicalOffset marker_offset( - InlineOffset(marker_fragment.Size().inline_size), - content_offset->block_offset); + LogicalOffset marker_offset(InlineOffset(marker_fragment.Size().inline_size), + content_offset->block_offset); // Adjust the block offset to align baselines of the marker and the content. NGLineHeightMetrics marker_metrics = marker_fragment.BaselineMetrics( @@ -117,7 +116,7 @@ bool NGUnpositionedListMarker::AddToBox( marker_offset.block_offset); DCHECK(container_builder); - container_builder->AddChild(*marker_layout_result, marker_offset); + container_builder->AddChild(marker_physical_fragment, marker_offset); return true; } @@ -129,18 +128,18 @@ LayoutUnit NGUnpositionedListMarker::AddToBoxWithoutLineBoxes( // Layout the list marker. scoped_refptr<const NGLayoutResult> marker_layout_result = Layout(space, container_builder->Style(), baseline_type); - DCHECK(marker_layout_result && marker_layout_result->PhysicalFragment()); + DCHECK(marker_layout_result); const NGPhysicalBoxFragment& marker_physical_fragment = - To<NGPhysicalBoxFragment>(*marker_layout_result->PhysicalFragment()); + To<NGPhysicalBoxFragment>(marker_layout_result->PhysicalFragment()); // When there are no line boxes, marker is top-aligned to the list item. // https://github.com/w3c/csswg-drafts/issues/2417 - NGLogicalSize marker_size = + LogicalSize marker_size = marker_physical_fragment.Size().ConvertToLogical(space.GetWritingMode()); - NGLogicalOffset offset(InlineOffset(marker_size.inline_size), LayoutUnit()); + LogicalOffset offset(InlineOffset(marker_size.inline_size), LayoutUnit()); DCHECK(container_builder); - container_builder->AddChild(*marker_layout_result, offset); + container_builder->AddChild(marker_physical_fragment, offset); return marker_size.block_size; } @@ -165,7 +164,7 @@ LayoutUnit NGUnpositionedListMarker::ComputeIntrudedFloatOffset( border_scrollbar_padding.inline_end; NGLayoutOpportunity opportunity = space.ExclusionSpace().FindLayoutOpportunity( - origin_offset, available_size, NGLogicalSize()); + origin_offset, available_size, LogicalSize()); DCHECK(marker_layout_object_); const TextDirection direction = marker_layout_object_->StyleRef().Direction(); if (direction == TextDirection::kLtr) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h b/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h index 3948d11c5b7..166b5b09a4b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h @@ -22,7 +22,7 @@ class NGBoxFragmentBuilder; class NGLayoutResult; class NGPhysicalFragment; -struct NGLogicalOffset; +struct LogicalOffset; // Represents an unpositioned list marker. // @@ -50,7 +50,7 @@ class CORE_EXPORT NGUnpositionedListMarker final { bool AddToBox(const NGConstraintSpace&, FontBaseline, const NGPhysicalFragment& content, - NGLogicalOffset* content_offset, + LogicalOffset* content_offset, NGBoxFragmentBuilder*, const NGBoxStrut&) const; @@ -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; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc index ce2e62a1f08..1a04dc32ac9 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc @@ -19,15 +19,36 @@ namespace blink { namespace { +// Tables need special handling, unfortunately. The code in this file assumes +// that if an element has a height or width specified, that's what its final +// height/width will be. Tables don't follow this pattern though; they treat +// specified height/width as a second min-height or min-width. +bool IsTable(const ComputedStyle& style) { + return style.Display() == EDisplay::kTable || + style.Display() == EDisplay::kInlineTable; +} + +inline Length TableAwareHeight(const ComputedStyle& style) { + if (IsTable(style)) + return Length::Auto(); + return style.Height(); +} + +inline Length TableAwareWidth(const ComputedStyle& style) { + if (IsTable(style)) + return Length::Auto(); + return style.Width(); +} + bool AbsoluteHorizontalNeedsEstimate(const ComputedStyle& style) { - const Length& width = style.Width(); + const Length& width = TableAwareWidth(style); return width.IsIntrinsic() || style.MinWidth().IsIntrinsic() || style.MaxWidth().IsIntrinsic() || (width.IsAuto() && (style.Left().IsAuto() || style.Right().IsAuto())); } bool AbsoluteVerticalNeedsEstimate(const ComputedStyle& style) { - const Length& height = style.Height(); + const Length& height = TableAwareHeight(style); return height.IsIntrinsic() || style.MinHeight().IsIntrinsic() || style.MaxHeight().IsIntrinsic() || (height.IsAuto() && (style.Top().IsAuto() || style.Bottom().IsAuto())); @@ -56,15 +77,24 @@ bool IsTopDominant(const WritingMode container_writing_mode, LayoutUnit ResolveMinWidth(const NGConstraintSpace& space, const ComputedStyle& style, const NGBoxStrut& border_padding, - const base::Optional<MinMaxSize>& child_minmax, - const Length& width) { + const base::Optional<MinMaxSize>& child_minmax) { + const Length& min_width = style.MinWidth(); if (space.GetWritingMode() == WritingMode::kHorizontalTb) { - return ResolveMinInlineLength(space, style, border_padding, child_minmax, - width, LengthResolvePhase::kLayout); + LayoutUnit resolved_min_width = + ResolveMinInlineLength(space, style, border_padding, child_minmax, + min_width, LengthResolvePhase::kLayout); + if (!IsTable(style)) + return resolved_min_width; + Length table_width = style.Width(); + if (table_width.IsAuto()) + return resolved_min_width; + LayoutUnit resolved_width = ResolveMainInlineLength( + space, style, border_padding, child_minmax, table_width); + return std::max(resolved_min_width, resolved_width); } LayoutUnit computed_width = child_minmax.has_value() ? child_minmax->max_size : LayoutUnit(); - return ResolveMinBlockLength(space, style, border_padding, width, + return ResolveMinBlockLength(space, style, border_padding, min_width, computed_width, LengthResolvePhase::kLayout); } @@ -101,15 +131,21 @@ LayoutUnit ResolveMainWidth(const NGConstraintSpace& space, LayoutUnit ResolveMinHeight(const NGConstraintSpace& space, const ComputedStyle& style, const NGBoxStrut& border_padding, - const base::Optional<MinMaxSize>& child_minmax, - const Length& height) { + const base::Optional<MinMaxSize>& child_minmax) { + const Length& min_height = style.MinHeight(); if (space.GetWritingMode() != WritingMode::kHorizontalTb) { - return ResolveMinInlineLength(space, style, border_padding, child_minmax, - height, LengthResolvePhase::kLayout); + LayoutUnit resolved_min_height = + ResolveMinInlineLength(space, style, border_padding, child_minmax, + min_height, LengthResolvePhase::kLayout); + if (!IsTable(style)) + return resolved_min_height; + LayoutUnit resolved_height = ResolveMainInlineLength( + space, style, border_padding, child_minmax, style.Height()); + return std::max(resolved_min_height, resolved_height); } LayoutUnit computed_height = child_minmax.has_value() ? child_minmax->max_size : LayoutUnit(); - return ResolveMinBlockLength(space, style, border_padding, height, + return ResolveMinBlockLength(space, style, border_padding, min_height, computed_height, LengthResolvePhase::kLayout); } @@ -229,9 +265,9 @@ void ComputeAbsoluteHorizontal(const NGConstraintSpace& space, if (!style.Right().IsAuto()) right = MinimumValueForLength(style.Right(), percentage_width); base::Optional<LayoutUnit> width = incoming_width; - NGPhysicalSize container_size = - ToNGPhysicalSize(space.AvailableSize(), space.GetWritingMode()); - DCHECK_NE(container_size.width, NGSizeIndefinite); + PhysicalSize container_size = + ToPhysicalSize(space.AvailableSize(), space.GetWritingMode()); + DCHECK_NE(container_size.width, kIndefiniteSize); // Solving the equation: // left + marginLeft + width + marginRight + right = container width @@ -332,21 +368,25 @@ void ComputeAbsoluteHorizontal(const NGConstraintSpace& space, container_size.width - *left - *right - *margin_left - *margin_right; } +#if DCHECK_IS_ON() // The DCHECK is useful, but only holds true when not saturated. if (!(left->MightBeSaturated() || right->MightBeSaturated() || width->MightBeSaturated() || margin_left->MightBeSaturated() || - margin_right->MightBeSaturated())) + margin_right->MightBeSaturated() || + container_size.width.MightBeSaturated())) { DCHECK_EQ(container_size.width, *left + *right + *margin_left + *margin_right + *width); + } +#endif // #if DCHECK_IS_ON() // If calculated width is outside of min/max constraints, // rerun the algorithm with constrained width. - LayoutUnit min = ResolveMinWidth(space, style, border_padding, child_minmax, - style.MinWidth()); + LayoutUnit min = ResolveMinWidth(space, style, border_padding, child_minmax); LayoutUnit max = ResolveMaxWidth(space, style, border_padding, child_minmax, style.MaxWidth()); - if (width != ConstrainByMinMax(*width, min, max)) { - width = ConstrainByMinMax(*width, min, max); + LayoutUnit constrained_width = ConstrainByMinMax(*width, min, max); + if (width != constrained_width) { + width = constrained_width; // Because this function only changes "width" when it's not already // set, it is safe to recursively call ourselves here because on the // second call it is guaranteed to be within min..max. @@ -400,9 +440,9 @@ void ComputeAbsoluteVertical(const NGConstraintSpace& space, bottom = MinimumValueForLength(style.Bottom(), percentage_height); base::Optional<LayoutUnit> height = incoming_height; - NGPhysicalSize container_size = - ToNGPhysicalSize(space.AvailableSize(), space.GetWritingMode()); - DCHECK_NE(container_size.height, NGSizeIndefinite); + PhysicalSize container_size = + ToPhysicalSize(space.AvailableSize(), space.GetWritingMode()); + DCHECK_NE(container_size.height, kIndefiniteSize); // Solving the equation: // top + marginTop + height + marginBottom + bottom @@ -493,17 +533,21 @@ void ComputeAbsoluteVertical(const NGConstraintSpace& space, height = container_size.height - *top - *bottom - *margin_top - *margin_bottom; } + +#if DCHECK_IS_ON() // The DCHECK is useful, but only holds true when not saturated. if (!(top->MightBeSaturated() || bottom->MightBeSaturated() || height->MightBeSaturated() || margin_top->MightBeSaturated() || - margin_bottom->MightBeSaturated())) { + margin_bottom->MightBeSaturated() || + container_size.height.MightBeSaturated())) { DCHECK_EQ(container_size.height, *top + *bottom + *margin_top + *margin_bottom + *height); } +#endif // #if DCHECK_IS_ON() + // If calculated height is outside of min/max constraints, // rerun the algorithm with constrained width. - LayoutUnit min = ResolveMinHeight(space, style, border_padding, child_minmax, - style.MinHeight()); + LayoutUnit min = ResolveMinHeight(space, style, border_padding, child_minmax); LayoutUnit max = ResolveMaxHeight(space, style, border_padding, child_minmax, style.MaxHeight()); if (height != ConstrainByMinMax(*height, min, max)) { @@ -603,13 +647,13 @@ NGAbsolutePhysicalPosition ComputePartialAbsoluteWithChildInlineSize( const NGBoxStrut& border_padding, const NGStaticPosition& static_position, const base::Optional<MinMaxSize>& child_minmax, - const base::Optional<NGLogicalSize>& replaced_size, + const base::Optional<LogicalSize>& replaced_size, const WritingMode container_writing_mode, const TextDirection container_direction) { NGAbsolutePhysicalPosition position; if (style.IsHorizontalWritingMode()) { base::Optional<LayoutUnit> width; - if (!style.Width().IsAuto()) { + if (!TableAwareWidth(style).IsAuto()) { width = ResolveMainWidth(space, style, border_padding, child_minmax, style.Width()); } else if (replaced_size.has_value()) { @@ -620,7 +664,7 @@ NGAbsolutePhysicalPosition ComputePartialAbsoluteWithChildInlineSize( container_writing_mode, container_direction, &position); } else { base::Optional<LayoutUnit> height; - if (!style.Height().IsAuto()) { + if (!TableAwareHeight(style).IsAuto()) { height = ResolveMainHeight(space, style, border_padding, child_minmax, style.Height()); } else if (replaced_size.has_value()) { @@ -639,7 +683,7 @@ void ComputeFullAbsoluteWithChildBlockSize( const NGBoxStrut& border_padding, const NGStaticPosition& static_position, const base::Optional<LayoutUnit>& child_block_size, - const base::Optional<NGLogicalSize>& replaced_size, + const base::Optional<LogicalSize>& replaced_size, const WritingMode container_writing_mode, const TextDirection container_direction, NGAbsolutePhysicalPosition* position) { @@ -653,7 +697,7 @@ void ComputeFullAbsoluteWithChildBlockSize( } if (style.IsHorizontalWritingMode()) { base::Optional<LayoutUnit> height; - if (!style.Height().IsAuto()) { + if (!TableAwareHeight(style).IsAuto()) { height = ResolveMainHeight(space, style, border_padding, child_minmax, style.Height()); } else if (replaced_size.has_value()) { @@ -664,7 +708,7 @@ void ComputeFullAbsoluteWithChildBlockSize( container_writing_mode, container_direction, position); } else { base::Optional<LayoutUnit> width; - if (!style.Width().IsAuto()) { + if (!TableAwareWidth(style).IsAuto()) { width = ResolveMainWidth(space, style, border_padding, child_minmax, style.Width()); } else if (replaced_size.has_value()) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h index 03a328c2632..67029cd9917 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h @@ -7,9 +7,9 @@ #include "base/optional.h" #include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_size.h" #include "third_party/blink/renderer/core/layout/min_max_size.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h" namespace blink { @@ -21,7 +21,7 @@ struct NGStaticPosition; struct CORE_EXPORT NGAbsolutePhysicalPosition { NGPhysicalBoxStrut inset; - NGPhysicalSize size; + PhysicalSize size; NGPhysicalBoxStrut margins; String ToString() const; }; @@ -65,18 +65,18 @@ ComputePartialAbsoluteWithChildInlineSize( const NGBoxStrut& border_padding, const NGStaticPosition&, const base::Optional<MinMaxSize>& child_minmax, - const base::Optional<NGLogicalSize>& replaced_size, + const base::Optional<LogicalSize>& replaced_size, const WritingMode container_writing_mode, const TextDirection container_direction); -// Compute rest of NGPhysicalRect that depends on child's block_size. +// Compute rest of PhysicalRect that depends on child's block_size. CORE_EXPORT void ComputeFullAbsoluteWithChildBlockSize( const NGConstraintSpace&, const ComputedStyle&, const NGBoxStrut& border_padding, const NGStaticPosition&, const base::Optional<LayoutUnit>& child_block_size, - const base::Optional<NGLogicalSize>& replaced_size, + const base::Optional<LogicalSize>& replaced_size, const WritingMode container_writing_mode, const TextDirection container_direction, NGAbsolutePhysicalPosition* position); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc index 7d33efb4ba0..20c8a4a4a50 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc @@ -35,7 +35,7 @@ class NGAbsoluteUtilsTest : public testing::Test { style_->SetBorderTopStyle(EBorderStyle::kSolid); style_->SetBorderBottomStyle(EBorderStyle::kSolid); style_->SetBoxSizing(EBoxSizing::kBorderBox); - container_size_ = NGLogicalSize(LayoutUnit(200), LayoutUnit(300)); + container_size_ = LogicalSize(LayoutUnit(200), LayoutUnit(300)); ltr_space_ = CreateConstraintSpace(TextDirection::kLtr, WritingMode::kHorizontalTb); @@ -90,7 +90,7 @@ class NGAbsoluteUtilsTest : public testing::Test { } scoped_refptr<ComputedStyle> style_; - NGLogicalSize container_size_; + LogicalSize container_size_; NGConstraintSpace ltr_space_; NGConstraintSpace rtl_space_; NGConstraintSpace vlr_space_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.cc index 5a73fc9d77c..bbc33e3ecf2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.cc @@ -9,6 +9,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.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_physical_fragment.h" @@ -28,16 +29,34 @@ void NGBaseLayoutAlgorithmTest::AdvanceToLayoutPhase() { GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kInPerformLayout); } +scoped_refptr<const NGPhysicalBoxFragment> +NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm( + NGBlockNode node, + const NGConstraintSpace& space, + const NGBreakToken* break_token) { + NGFragmentGeometry fragment_geometry = + CalculateInitialFragmentGeometry(space, node); + + scoped_refptr<const NGLayoutResult> result = + NGBlockLayoutAlgorithm( + {node, fragment_geometry, space, To<NGBlockBreakToken>(break_token)}) + .Layout(); + + return To<NGPhysicalBoxFragment>(&result->PhysicalFragment()); +} + std::pair<scoped_refptr<const NGPhysicalBoxFragment>, NGConstraintSpace> NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithmForElement(Element* element) { auto* block_flow = To<LayoutBlockFlow>(element->GetLayoutObject()); NGBlockNode node(block_flow); NGConstraintSpace space = NGConstraintSpace::CreateFromLayoutObject(*block_flow); + NGFragmentGeometry fragment_geometry = + CalculateInitialFragmentGeometry(space, node); scoped_refptr<const NGLayoutResult> result = - NGBlockLayoutAlgorithm(node, space).Layout(); - return std::make_pair(To<NGPhysicalBoxFragment>(result->PhysicalFragment()), + NGBlockLayoutAlgorithm({node, fragment_geometry, space}).Layout(); + return std::make_pair(To<NGPhysicalBoxFragment>(&result->PhysicalFragment()), std::move(space)); } @@ -57,7 +76,7 @@ const NGPhysicalBoxFragment* NGBaseLayoutAlgorithmTest::CurrentFragmentFor( } const NGPhysicalBoxFragment* FragmentChildIterator::NextChild( - NGPhysicalOffset* fragment_offset) { + PhysicalOffset* fragment_offset) { if (!parent_) return nullptr; if (index_ >= parent_->Children().size()) @@ -77,12 +96,12 @@ const NGPhysicalBoxFragment* FragmentChildIterator::NextChild( NGConstraintSpace ConstructBlockLayoutTestConstraintSpace( WritingMode writing_mode, TextDirection direction, - NGLogicalSize size, + LogicalSize size, bool shrink_to_fit, bool is_new_formatting_context, LayoutUnit fragmentainer_space_available) { NGFragmentationType block_fragmentation = - fragmentainer_space_available != NGSizeIndefinite + fragmentainer_space_available != kIndefiniteSize ? NGFragmentationType::kFragmentColumn : NGFragmentationType::kFragmentNone; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h index b70b1fe114d..8459fe2838a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h @@ -6,7 +6,7 @@ #define NG_BASE_LAYOUT_ALGORITHM_TEST_H_ #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h" @@ -18,6 +18,7 @@ namespace blink { class Element; class LayoutNGBlockFlow; +class NGBlockNode; class NGPhysicalBoxFragment; // Base class for all LayoutNG Algorithms unit test classes. @@ -32,6 +33,11 @@ class NGBaseLayoutAlgorithmTest // RunBlockLayoutAlgorithmForElement. void AdvanceToLayoutPhase(); + scoped_refptr<const NGPhysicalBoxFragment> RunBlockLayoutAlgorithm( + NGBlockNode node, + const NGConstraintSpace& space, + const NGBreakToken* break_token = nullptr); + std::pair<scoped_refptr<const NGPhysicalBoxFragment>, NGConstraintSpace> RunBlockLayoutAlgorithmForElement(Element* element); @@ -55,7 +61,7 @@ class FragmentChildIterator { } const NGPhysicalBoxFragment* NextChild( - NGPhysicalOffset* fragment_offset = nullptr); + PhysicalOffset* fragment_offset = nullptr); private: const NGPhysicalBoxFragment* parent_; @@ -65,10 +71,10 @@ class FragmentChildIterator { NGConstraintSpace ConstructBlockLayoutTestConstraintSpace( WritingMode writing_mode, TextDirection direction, - NGLogicalSize size, + LogicalSize size, bool shrink_to_fit = false, bool is_new_formatting_context = false, - LayoutUnit fragmentainer_space_available = NGSizeIndefinite); + LayoutUnit fragmentainer_space_available = kIndefiniteSize); } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.cc index 005058185a1..430b8bdde8a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.cc @@ -73,7 +73,7 @@ const NGInlineBreakToken* NGBlockBreakToken::InlineBreakTokenFor( return nullptr; } -#ifndef NDEBUG +#if DCHECK_IS_ON() String NGBlockBreakToken::ToString() const { StringBuilder string_builder; @@ -84,6 +84,6 @@ String NGBlockBreakToken::ToString() const { return string_builder.ToString(); } -#endif // NDEBUG +#endif // DCHECK_IS_ON() } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.h index bf2dd8c06d3..18a44a94fee 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.h @@ -114,7 +114,7 @@ class CORE_EXPORT NGBlockBreakToken final : public NGBreakToken { const NGInlineBreakToken* InlineBreakTokenFor(const NGLayoutInputNode&) const; const NGInlineBreakToken* InlineBreakTokenFor(const LayoutBox&) const; -#ifndef NDEBUG +#if DCHECK_IS_ON() String ToString() const override; #endif diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc index 743f78fa150..9cce428b4f7 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc @@ -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" @@ -35,6 +36,16 @@ namespace blink { namespace { +inline scoped_refptr<const NGLayoutResult> LayoutInflow( + const NGConstraintSpace& space, + const NGBreakToken* break_token, + NGLayoutInputNode* node, + NGInlineChildLayoutContext* context) { + auto* inline_node = DynamicTo<NGInlineNode>(node); + return inline_node ? inline_node->Layout(space, break_token, context) + : To<NGBlockNode>(node)->Layout(space, break_token); +} + // Return true if a child is to be cleared past adjoining floats. These are // floats that would otherwise (if 'clear' were 'none') be pulled down by the // BFC block offset of the child. If the child is to clear floats, though, we @@ -70,45 +81,13 @@ inline bool HasClearancePastAdjoiningFloats(NGFloatTypes adjoining_floats, bool ApplyClearance(const NGConstraintSpace& constraint_space, LayoutUnit* bfc_block_offset) { if (constraint_space.HasClearanceOffset() && - (*bfc_block_offset < constraint_space.ClearanceOffset() || - constraint_space.ShouldForceClearance())) { + *bfc_block_offset < constraint_space.ClearanceOffset()) { *bfc_block_offset = constraint_space.ClearanceOffset(); return true; } return false; } -// Returns if the resulting fragment should be considered an "empty block". -// There is special casing for fragments like this, e.g. margins "collapse -// through", etc. -inline bool IsEmptyBlock(bool is_new_fc, const NGLayoutResult& layout_result) { - // TODO(ikilpatrick): This should be a DCHECK. - if (is_new_fc) - return false; - - if (layout_result.BfcBlockOffset()) - return false; - -#if DCHECK_IS_ON() - const NGPhysicalFragment& physical_fragment = - *layout_result.PhysicalFragment(); - // This just checks that the fragments block size is actually zero. We can - // assume that its in the same writing mode as its parent, as a different - // writing mode child will be caught by the is_new_fc check. - NGFragment fragment(physical_fragment.Style().GetWritingMode(), - physical_fragment); - DCHECK_EQ(LayoutUnit(), fragment.BlockSize()); -#endif - - return true; -} - -// As above; for convenience if you have a child_space. -inline bool IsEmptyBlock(const NGConstraintSpace& child_space, - const NGLayoutResult& layout_result) { - return IsEmptyBlock(child_space.IsNewFormattingContext(), layout_result); -} - LayoutUnit LogicalFromBfcLineOffset(LayoutUnit child_bfc_line_offset, LayoutUnit parent_bfc_line_offset, LayoutUnit child_inline_size, @@ -127,11 +106,11 @@ LayoutUnit LogicalFromBfcLineOffset(LayoutUnit child_bfc_line_offset, return inline_offset; } -NGLogicalOffset LogicalFromBfcOffsets(const NGBfcOffset& child_bfc_offset, - const NGBfcOffset& parent_bfc_offset, - LayoutUnit child_inline_size, - LayoutUnit parent_inline_size, - TextDirection direction) { +LogicalOffset LogicalFromBfcOffsets(const NGBfcOffset& child_bfc_offset, + const NGBfcOffset& parent_bfc_offset, + LayoutUnit child_inline_size, + LayoutUnit parent_inline_size, + TextDirection direction) { LayoutUnit inline_offset = LogicalFromBfcLineOffset( child_bfc_offset.line_offset, parent_bfc_offset.line_offset, child_inline_size, parent_inline_size, direction); @@ -164,13 +143,17 @@ void StopMarginCollapsing(EMarginCollapse collapse_value, } // namespace NGBlockLayoutAlgorithm::NGBlockLayoutAlgorithm( - NGBlockNode node, - const NGConstraintSpace& space, - const NGBlockBreakToken* break_token) - : NGLayoutAlgorithm(node, space, break_token), - is_resuming_(break_token && !break_token->IsBreakBefore()), - exclusion_space_(space.ExclusionSpace()) { - container_builder_.SetIsNewFormattingContext(space.IsNewFormattingContext()); + const NGLayoutAlgorithmParams& params) + : NGLayoutAlgorithm(params), + border_padding_(params.fragment_geometry.border + + params.fragment_geometry.padding), + border_scrollbar_padding_(border_padding_ + + params.fragment_geometry.scrollbar), + is_resuming_(IsResumingLayout(params.break_token)), + exclusion_space_(params.space.ExclusionSpace()) { + container_builder_.SetIsNewFormattingContext( + params.space.IsNewFormattingContext()); + container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); } // Define the destructor here, so that we can forward-declare more in the @@ -183,22 +166,15 @@ void NGBlockLayoutAlgorithm::SetBoxType(NGPhysicalFragment::NGBoxType type) { base::Optional<MinMaxSize> NGBlockLayoutAlgorithm::ComputeMinMaxSize( const MinMaxSizeInput& input) const { - NGBoxStrut border_padding = ComputeBorders(ConstraintSpace(), Node()) + - ComputePadding(ConstraintSpace(), Style()); - MinMaxSize sizes; - - // Size-contained elements don't consider their contents for intrinsic sizing. - if (node_.ShouldApplySizeContainment()) { - if (input.size_type == NGMinMaxSizeType::kBorderBoxSize) { - sizes = - border_padding.InlineSum() + Node().GetScrollbarSizes().InlineSum(); - } + base::Optional<MinMaxSize> sizes = CalculateMinMaxSizesIgnoringChildren( + node_, border_scrollbar_padding_, input.size_type); + if (sizes) return sizes; - } + sizes.emplace(); LayoutUnit child_percentage_resolution_block_size = CalculateChildPercentageBlockSizeForMinMax( - ConstraintSpace(), Node(), border_padding, + ConstraintSpace(), Node(), border_padding_, input.percentage_resolution_block_size); const TextDirection direction = Style().Direction(); @@ -225,7 +201,7 @@ base::Optional<MinMaxSize> NGBlockLayoutAlgorithm::ComputeMinMaxSize( float_left_inline_size + float_right_inline_size; if (child_clear != EClear::kNone) - sizes.max_size = std::max(sizes.max_size, float_inline_size); + sizes->max_size = std::max(sizes->max_size, float_inline_size); if (child_clear == EClear::kBoth || child_clear == EClear::kLeft) float_left_inline_size = LayoutUnit(); @@ -304,13 +280,13 @@ base::Optional<MinMaxSize> NGBlockLayoutAlgorithm::ComputeMinMaxSize( // This is just a standard inflow child. max_inline_contribution = child_sizes.max_size + margins.InlineSum(); } - sizes.max_size = std::max(sizes.max_size, max_inline_contribution); + sizes->max_size = std::max(sizes->max_size, max_inline_contribution); // The min inline contribution just assumes that floats are all on their own // "line". LayoutUnit min_inline_contribution = child_sizes.min_size + margins.InlineSum(); - sizes.min_size = std::max(sizes.min_size, min_inline_contribution); + sizes->min_size = std::max(sizes->min_size, min_inline_contribution); // Anything that isn't a float will create a new "line" resetting the float // size trackers. @@ -320,22 +296,22 @@ base::Optional<MinMaxSize> NGBlockLayoutAlgorithm::ComputeMinMaxSize( } } - DCHECK_GE(sizes.min_size, LayoutUnit()); - DCHECK_LE(sizes.min_size, sizes.max_size) << Node().ToString(); + DCHECK_GE(sizes->min_size, LayoutUnit()); + DCHECK_LE(sizes->min_size, sizes->max_size) << Node().ToString(); if (input.size_type == NGMinMaxSizeType::kBorderBoxSize) - sizes += border_padding.InlineSum() + node_.GetScrollbarSizes().InlineSum(); + *sizes += border_scrollbar_padding_.InlineSum(); return sizes; } -NGLogicalOffset NGBlockLayoutAlgorithm::CalculateLogicalOffset( +LogicalOffset NGBlockLayoutAlgorithm::CalculateLogicalOffset( const NGFragment& fragment, LayoutUnit child_bfc_line_offset, const base::Optional<LayoutUnit>& child_bfc_block_offset) { LayoutUnit inline_size = container_builder_.Size().inline_size; TextDirection direction = ConstraintSpace().Direction(); - if (child_bfc_block_offset) { + if (child_bfc_block_offset && container_builder_.BfcBlockOffset()) { return LogicalFromBfcOffsets( {child_bfc_line_offset, *child_bfc_block_offset}, ContainerBfcOffset(), fragment.InlineSize(), inline_size, direction); @@ -345,10 +321,9 @@ NGLogicalOffset NGBlockLayoutAlgorithm::CalculateLogicalOffset( child_bfc_line_offset, container_builder_.BfcLineOffset(), fragment.InlineSize(), inline_size, direction); - // If we've reached here, both the child and the current layout don't have a - // BFC block offset yet. Children in this situation are always placed at a - // logical block offset of 0. - DCHECK(!container_builder_.BfcBlockOffset()); + // If we've reached here, either the parent, or the child don't have a BFC + // block-offset yet. Children in this situation are always placed at a + // logical block-offset of zero. return {inline_offset, LayoutUnit()}; } @@ -369,34 +344,10 @@ NGBlockLayoutAlgorithm::LayoutWithInlineChildLayoutContext() { inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( NGInlineChildLayoutContext* inline_child_layout_context) { - container_builder_.SetBorders(ComputeBorders(ConstraintSpace(), Node())); - container_builder_.SetPadding(ComputePadding(ConstraintSpace(), Style())); - border_padding_ = container_builder_.Borders() + container_builder_.Padding(); - - NGBoxStrut scrollbars = Node().GetScrollbarSizes(); - border_scrollbar_padding_ = ConstraintSpace().IsAnonymous() - ? NGBoxStrut() - : border_padding_ + scrollbars; - NGLogicalSize border_box_size = CalculateBorderBoxSize( - ConstraintSpace(), Node(), border_padding_, - CalculateDefaultBlockSize(ConstraintSpace(), Node(), - border_scrollbar_padding_)); - + LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); child_available_size_ = ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); - // When the content box is smaller than the scrollbar, clamp the scrollbar. - if (UNLIKELY(!child_available_size_.inline_size && scrollbars.InlineSum() && - ClampScrollbarToContentBox( - &scrollbars, - border_box_size.inline_size - border_padding_.InlineSum()) && - !ConstraintSpace().IsAnonymous())) { - // Re-compute dependent values if scrollbar size was clamped. - border_scrollbar_padding_ = border_padding_ + scrollbars; - child_available_size_ = - ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); - } - child_percentage_size_ = CalculateChildPercentageSize( ConstraintSpace(), Node(), child_available_size_); replaced_child_percentage_size_ = CalculateReplacedChildPercentageSize( @@ -405,13 +356,11 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( // All of the above calculations with border_scrollbar_padding_ shouldn't // include the table cell's intrinsic padding. We can now add this. - NGBoxStrut intrinsic_padding = + border_scrollbar_padding_ += ComputeIntrinsicPadding(ConstraintSpace(), Node()); - border_scrollbar_padding_ += intrinsic_padding; if (ConstraintSpace().HasBlockFragmentation()) container_builder_.SetHasBlockFragmentation(); - container_builder_.SetInlineSize(border_box_size.inline_size); container_builder_.SetBfcLineOffset( ConstraintSpace().BfcOffset().line_offset); @@ -425,7 +374,7 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( // offset is updated. abort_when_bfc_block_offset_updated_ = true; - container_builder_.AddAdjoiningFloatTypes(float_types); + container_builder_.SetAdjoiningFloatTypes(float_types); } // If we are resuming from a break token our start border and padding is @@ -435,7 +384,7 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( NGPreviousInflowPosition previous_inflow_position = { LayoutUnit(), ConstraintSpace().MarginStrut(), - /* empty_block_affected_by_clearance */ false}; + /* self_collapsing_child_had_clearance */ false}; // Do not collapse margins between parent and its child if: // @@ -485,9 +434,9 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( DCHECK(ConstraintSpace().MarginStrut().IsEmpty()); if (!container_builder_.BfcBlockOffset()) { - // New formatting contexts, and where we have an empty block affected by - // clearance should already have their BFC block offset resolved. - DCHECK(!previous_inflow_position.empty_block_affected_by_clearance); + // New formatting-contexts, and when we have a self-collapsing child + // affected by clearance must already have their BFC block-offset resolved. + DCHECK(!previous_inflow_position.self_collapsing_child_had_clearance); DCHECK(!ConstraintSpace().IsNewFormattingContext()); } #endif @@ -515,7 +464,14 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( // When possible, this adds fragments to |container_builder_| and update // |previous_inflow_position| and |BreakToken()|. scoped_refptr<const NGInlineBreakToken> previous_inline_break_token; - NGBlockChildIterator child_iterator(Node().FirstChild(), BreakToken()); + + // If this layout is blocked by a display-lock, then we pretend this node has + // no children. Due to this, we skip layout on these children. + NGBlockChildIterator child_iterator( + Node().LayoutBlockedByDisplayLock(DisplayLockContext::kChildren) + ? NGBlockNode(nullptr) + : Node().FirstChild(), + BreakToken()); for (auto entry = child_iterator.NextChild(); NGLayoutInputNode child = entry.node; entry = child_iterator.NextChild(previous_inline_break_token.get())) { @@ -537,7 +493,7 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( // all the way to the root of the fragmentation context without finding // any such container, we have no valid class A break point, and if a // forced break was requested, none will be inserted. - if (!child.IsInline()) + if (!child.IsInline() && ConstraintSpace().HasBlockFragmentation()) container_builder_.SetInitialBreakBefore(child.Style().BreakBefore()); bool abort; @@ -572,12 +528,13 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( // function is continued within |FinishLayout|. However it should be read as // one function. return FinishLayout(&previous_inflow_position, border_box_size, - container_builder_.Borders(), scrollbars); + container_builder_.Borders(), + container_builder_.Scrollbar()); } scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( NGPreviousInflowPosition* previous_inflow_position, - NGLogicalSize border_box_size, + LogicalSize border_box_size, const NGBoxStrut& borders, const NGBoxStrut& scrollbars) { NGMarginStrut end_margin_strut = previous_inflow_position->margin_strut; @@ -592,11 +549,11 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( // The end margin strut of an in-flow fragment contributes to the size of the // current fragment if: // - There is block-end border/scrollbar/padding. - // - There was empty block(s) affected by clearance. + // - There was a self-collapsing child affected by clearance. // - We are a new formatting context. // Additionally this fragment produces no end margin strut. if (border_scrollbar_padding_.block_end || - previous_inflow_position->empty_block_affected_by_clearance || + previous_inflow_position->self_collapsing_child_had_clearance || ConstraintSpace().IsNewFormattingContext()) { // If we are a quirky container, we ignore any quirky margins and // just consider normal margins to extend our size. Other UAs @@ -647,24 +604,20 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( intrinsic_block_size_ = std::max(intrinsic_block_size_, CalculateMinimumBlockSize(end_margin_strut)); - // With contain:size we need to ignore all kinds of intrinsic sizing. If block - // height was specified as auto, its content-box size will become 0. - if (Node().ShouldApplySizeContainment()) - intrinsic_block_size_ = border_scrollbar_padding_.BlockSum(); + // TODO(layout-dev): Is CalculateMinimumBlockSize common to other algorithms, + // and should move into ClampIntrinsicBlockSize? + intrinsic_block_size_ = ClampIntrinsicBlockSize( + Node(), border_scrollbar_padding_, intrinsic_block_size_); // Recompute the block-axis size now that we know our content size. - // NOTE: For table cells, the block-size is just the intrinsic block-size. - border_box_size.block_size = - Node().IsTableCell() - ? intrinsic_block_size_ - : ComputeBlockSizeForFragment(ConstraintSpace(), Style(), - border_padding_, intrinsic_block_size_); + border_box_size.block_size = ComputeBlockSizeForFragment( + ConstraintSpace(), Node(), border_padding_, intrinsic_block_size_); container_builder_.SetBlockSize(border_box_size.block_size); - // If our BFC block offset is still unknown, there's one last thing to take - // into consideration: Non-empty blocks always know their position in space. - // If we have a break token, it means that we know the blocks' position even - // if they're empty; it will be at the very start of the fragmentainer. + // If our BFC block-offset is still unknown, we check: + // - If we have a non-zero block-size (margins don't collapse through us). + // - If we have a break token. (Even if we are self-collapsing we position + // ourselves at the very start of the fragmentainer). if (!container_builder_.BfcBlockOffset() && (border_box_size.block_size || BreakToken())) { if (!ResolveBfcBlockOffset(previous_inflow_position)) @@ -674,9 +627,11 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( if (container_builder_.BfcBlockOffset()) { // Do not collapse margins between the last in-flow child and bottom margin - // of its parent if the parent has height != auto. - if (!Style().LogicalHeight().IsAuto()) { - // TODO(layout-ng): handle LogicalMinHeight, LogicalMaxHeight. + // of its parent if: + // - The parent has block-size != auto. + // - The block-size differs from the intrinsic size. + if (!Style().LogicalHeight().IsAuto() || + border_box_size.block_size != intrinsic_block_size_) { end_margin_strut = NGMarginStrut(); } } @@ -690,6 +645,25 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( container_builder_.SetEndMarginStrut(end_margin_strut); container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_); + if (container_builder_.BfcBlockOffset()) { + // If we know our BFC block-offset we should have correctly placed all + // adjoining floats, and shouldn't propagate this information to siblings. + container_builder_.ResetAdjoiningFloatTypes(); + } else { + // If we don't know our BFC block-offset yet, we know that for + // margin-collapsing purposes we are self-collapsing. + container_builder_.SetIsSelfCollapsing(); + + // If we've been forced at a particular BFC block-offset, (either from + // clearance past adjoining floats, or a re-layout), we can safely set our + // BFC block-offset now. + if (ConstraintSpace().ForcedBfcBlockOffset()) { + DCHECK(unpositioned_floats_.IsEmpty()); + container_builder_.SetBfcBlockOffset( + *ConstraintSpace().ForcedBfcBlockOffset()); + } + } + // We only finalize for fragmentation if the fragment has a BFC block offset. // This may occur with a zero block size fragment. We need to know the BFC // block offset to determine where the fragmentation line is relative to us. @@ -713,6 +687,11 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( DCHECK(!container_builder_.BfcBlockOffset()); DCHECK(container_builder_.AdjoiningFloatTypes()); } + + // If we're not participating in a fragmentation context, no block + // fragmentation related fields should have been set. + if (!ConstraintSpace().HasBlockFragmentation()) + container_builder_.CheckNoBlockFragmentation(); #endif PropagateBaselinesFromChildren(); @@ -732,6 +711,7 @@ const NGInlineBreakToken* NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache( NGInlineNode inline_node, NGPreviousInflowPosition* previous_inflow_position, bool* aborted_out) { + DCHECK(RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()); LayoutBox* layout_box = inline_node.GetLayoutBox(); if (layout_box->SelfNeedsLayout()) return nullptr; @@ -764,58 +744,45 @@ const NGInlineBreakToken* NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache( TextDirection direction = container_builder_.Direction(); DCHECK_EQ(writing_mode, lineboxes->Style().GetWritingMode()); DCHECK_EQ(direction, lineboxes->Style().Direction()); - const NGPhysicalSize outer_size = lineboxes->Size(); - - struct FragmentWithLogicalOffset { - const NGPhysicalFragment& fragment; - NGLogicalOffset offset; - }; - // Avoid calling |ReserveInitialCapacity()| because - // |lineboxes->Children().size()| is O(n), not linear, but it's critical to - // have enough capacity. - Vector<FragmentWithLogicalOffset, 64> fragments; + const PhysicalSize outer_size = lineboxes->Size(); + + LayoutUnit used_block_size = previous_inflow_position->logical_block_offset; + NGBreakToken* last_break_token = nullptr; for (const NGPaintFragment* child : lineboxes->Children()) { if (child->IsDirty()) break; - // Abort if there are floats, oof, or list marker. They need re-layout. - const NGPhysicalFragment& child_fragment = child->PhysicalFragment(); - if (!child_fragment.IsLineBox()) - return nullptr; - - NGLogicalOffset logical_offset = child->Offset().ConvertToLogical( - writing_mode, direction, outer_size, child_fragment.Size()); - fragments.push_back( - FragmentWithLogicalOffset{child_fragment, logical_offset}); - } - if (fragments.IsEmpty()) - return nullptr; + // Abort if the line propagated its descendants to outside of the line. They + // are propagated through NGLayoutResult, which we don't cache. + const NGPhysicalLineBoxFragment* line = + DynamicTo<NGPhysicalLineBoxFragment>(&child->PhysicalFragment()); + if (!line || line->HasPropagatedDescendants()) + break; - // TODO(kojii): Running the normal layout code at least once for this child - // helps reducing the code to setup internal states after the reuse. Remove - // the last fragment if it is the end of the fragmentation to do so, but we - // should figure out how to setup the states without doing this. - DCHECK(fragments.back().fragment.BreakToken()); - if (fragments.back().fragment.BreakToken()->IsFinished()) { - fragments.Shrink(fragments.size() - 1); - if (fragments.IsEmpty()) - return nullptr; - } + // TODO(kojii): Running the normal layout code at least once for this child + // helps reducing the code to setup internal states after the reuse. Remove + // the last fragment if it is the end of the fragmentation to do so, but we + // should figure out how to setup the states without doing this. + NGBreakToken* break_token = line->BreakToken(); + DCHECK(break_token); + if (break_token->IsFinished()) + break; - for (const auto& fragment : fragments) { - container_builder_.AddChild(&fragment.fragment, fragment.offset); + last_break_token = break_token; + LogicalOffset logical_offset = child->Offset().ConvertToLogical( + writing_mode, direction, outer_size, line->Size()); + container_builder_.AddChild( + *line, {logical_offset.inline_offset, used_block_size}); + used_block_size += line->Size().ConvertToLogical(writing_mode).block_size; } + if (!last_break_token) + return nullptr; // Update the internal states to after the re-used fragments. - const auto& last_fragment = fragments.back(); - LayoutUnit used_block_size = - last_fragment.offset.block_offset + - last_fragment.fragment.Size().ConvertToLogical(writing_mode).block_size; previous_inflow_position->logical_block_offset = used_block_size; // In order to layout the rest of lines, return the break token from the last // reused line box. - NGBreakToken* last_break_token = last_fragment.fragment.BreakToken(); DCHECK(last_break_token); DCHECK(!last_break_token->IsFinished()); return To<NGInlineBreakToken>(last_break_token); @@ -824,57 +791,51 @@ const NGInlineBreakToken* NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache( void NGBlockLayoutAlgorithm::HandleOutOfFlowPositioned( const NGPreviousInflowPosition& previous_inflow_position, NGBlockNode child) { - const ComputedStyle& child_style = child.Style(); - - NGLogicalOffset 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 */ NGLogicalSize()); - - 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()) { + // The static-position of inline-level OOF-positioned nodes depends on + // previous floats (if any). + // + // Due to this we need to mark this node as having adjoining objects, and + // perform a re-layout if our position shifts. + if (!container_builder_.BfcBlockOffset()) { + container_builder_.AddAdjoiningFloatTypes(kAdjoiningInlineOutOfFlow); + abort_when_bfc_block_offset_updated_ = true; + } + + LayoutUnit origin_bfc_block_offset = + container_builder_.BfcBlockOffset().value_or( + ConstraintSpace().ForcedBfcBlockOffset().value_or( + ConstraintSpace().BfcOffset().block_offset)) + + static_offset.block_offset; + + NGBfcOffset origin_bfc_offset = { + ConstraintSpace().BfcOffset().line_offset + + border_scrollbar_padding_.LineLeft(Style().Direction()), + origin_bfc_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( const NGPreviousInflowPosition& previous_inflow_position, NGBlockNode child, const NGBlockBreakToken* child_break_token) { - // If there is a break token for a float we must be resuming layout, we must - // always know our position in the BFC. - DCHECK(!child_break_token || child_break_token->IsBreakBefore() || + // If we're resuming layout, we must always know our position in the BFC. + DCHECK(!IsResumingLayout(child_break_token) || container_builder_.BfcBlockOffset()); NGUnpositionedFloat unpositioned_float(child, child_break_token); @@ -892,14 +853,14 @@ void NGBlockLayoutAlgorithm::HandleFloat( // No need to postpone the positioning if we know the correct offset. if (container_builder_.BfcBlockOffset() || - ConstraintSpace().FloatsBfcBlockOffset()) { + ConstraintSpace().ForcedBfcBlockOffset()) { // Adjust origin point to the margins of the last child. // Example: <div style="margin-bottom: 20px"><float></div> // <div style="margin-bottom: 30px"></div> LayoutUnit origin_block_offset = container_builder_.BfcBlockOffset() ? NextBorderEdge(previous_inflow_position) - : *ConstraintSpace().FloatsBfcBlockOffset(); + : *ConstraintSpace().ForcedBfcBlockOffset(); PositionPendingFloats(origin_block_offset); } } @@ -916,11 +877,9 @@ bool NGBlockLayoutAlgorithm::HandleNewFormattingContext( const ComputedStyle& child_style = child.Style(); const TextDirection direction = ConstraintSpace().Direction(); - bool has_clearance_past_adjoining_floats = HasClearancePastAdjoiningFloats( - container_builder_.AdjoiningFloatTypes(), child_style, Style()); - NGInflowChildData child_data = ComputeChildData( - *previous_inflow_position, child, child_break_token, - has_clearance_past_adjoining_floats, /* is_new_fc */ true); + NGInflowChildData child_data = + ComputeChildData(*previous_inflow_position, child, child_break_token, + /* is_new_fc */ true); LayoutUnit child_origin_line_offset = ConstraintSpace().BfcOffset().line_offset + @@ -954,11 +913,22 @@ bool NGBlockLayoutAlgorithm::HandleNewFormattingContext( if (!container_builder_.BfcBlockOffset()) { had_pending_floats = !unpositioned_floats_.IsEmpty(); - if (ConstraintSpace().FloatsBfcBlockOffset()) { + // If this node, or an arbitrary ancestor had clearance past adjoining + // floats, we consider the margin "separated". We should *never* attempt to + // re-resolve the BFC block-offset in this case. + bool has_clearance_past_adjoining_floats = + ConstraintSpace().AncestorHasClearancePastAdjoiningFloats() || + HasClearancePastAdjoiningFloats( + container_builder_.AdjoiningFloatTypes(), child_style, Style()); + + if (has_clearance_past_adjoining_floats) { + child_bfc_offset_estimate = NextBorderEdge(*previous_inflow_position); + child_margin_got_separated = true; + } else if (ConstraintSpace().ForcedBfcBlockOffset()) { // This is not the first time we're here. We already have a suggested BFC // block offset. bfc_offset_already_resolved = true; - child_bfc_offset_estimate = *ConstraintSpace().FloatsBfcBlockOffset(); + child_bfc_offset_estimate = *ConstraintSpace().ForcedBfcBlockOffset(); // We require that the BFC block offset be the one we'd get with either // margins adjoining or margins separated. Anything else is a bug. DCHECK(child_bfc_offset_estimate == adjoining_bfc_offset_estimate || @@ -967,9 +937,6 @@ bool NGBlockLayoutAlgorithm::HandleNewFormattingContext( // margin strut or not. child_margin_got_separated = child_bfc_offset_estimate != adjoining_bfc_offset_estimate; - } else if (has_clearance_past_adjoining_floats) { - child_bfc_offset_estimate = NextBorderEdge(*previous_inflow_position); - child_margin_got_separated = true; } // The BFC block offset of this container gets resolved because of this @@ -1021,8 +988,15 @@ bool NGBlockLayoutAlgorithm::HandleNewFormattingContext( // re-resolve the BFC block offset without the child's margin. LayoutUnit old_offset = *container_builder_.BfcBlockOffset(); container_builder_.ResetBfcBlockOffset(); + + // Re-resolving the BFC block-offset with a different "forced" BFC + // block-offset is only safe if an ancestor *never* had clearance past + // adjoining floats. + DCHECK(!ConstraintSpace().AncestorHasClearancePastAdjoiningFloats()); ResolveBfcBlockOffset(previous_inflow_position, - non_adjoining_bfc_offset_estimate); + non_adjoining_bfc_offset_estimate, + /* forced_bfc_block_offset */ base::nullopt); + if ((bfc_offset_already_resolved || had_pending_floats) && old_offset != *container_builder_.BfcBlockOffset()) { // The first BFC block offset resolution turned out to be wrong, and we @@ -1047,9 +1021,9 @@ bool NGBlockLayoutAlgorithm::HandleNewFormattingContext( {child_origin_line_offset, child_bfc_offset_estimate}, /* abort_if_cleared */ false); } - DCHECK(layout_result->PhysicalFragment()); - const auto& physical_fragment = *layout_result->PhysicalFragment(); - NGFragment fragment(ConstraintSpace().GetWritingMode(), physical_fragment); + + NGFragment fragment(ConstraintSpace().GetWritingMode(), + layout_result->PhysicalFragment()); // Auto-margins are applied within the layout opportunity which fits. We'll // pretend that computed margins are 0 here, as they have already been @@ -1064,8 +1038,14 @@ bool NGBlockLayoutAlgorithm::HandleNewFormattingContext( auto_margins.inline_end = opportunity.rect.InlineSize() - fragment.InlineSize() - auto_margins.inline_start; } else { + LayoutUnit inline_size = fragment.InlineSize(); + // Negative margins are not used to determine opportunity, but need to take + // them into account for positioning. + LayoutUnit inline_margin = child_data.margins.InlineSum(); + if (inline_margin < 0) + inline_size += inline_margin; ResolveInlineMargins(child_style, Style(), opportunity.rect.InlineSize(), - fragment.InlineSize(), &auto_margins); + inline_size, &auto_margins); } LayoutUnit child_bfc_line_offset = opportunity.rect.start_offset.line_offset + @@ -1090,7 +1070,7 @@ bool NGBlockLayoutAlgorithm::HandleNewFormattingContext( NGBfcOffset child_bfc_offset(child_bfc_line_offset, opportunity.rect.start_offset.block_offset); - NGLogicalOffset logical_offset = LogicalFromBfcOffsets( + LogicalOffset logical_offset = LogicalFromBfcOffsets( child_bfc_offset, ContainerBfcOffset(), fragment.InlineSize(), container_builder_.Size().inline_size, ConstraintSpace().Direction()); @@ -1109,7 +1089,8 @@ bool NGBlockLayoutAlgorithm::HandleNewFormattingContext( PositionOrPropagateListMarker(*layout_result, &logical_offset); - container_builder_.AddChild(*layout_result, logical_offset); + container_builder_.AddChild(layout_result->PhysicalFragment(), + logical_offset); container_builder_.PropagateBreak(*layout_result); // The margins we store will be used by e.g. getComputedStyle(). @@ -1122,7 +1103,7 @@ bool NGBlockLayoutAlgorithm::HandleNewFormattingContext( *previous_inflow_position = ComputeInflowPosition( *previous_inflow_position, child, child_data, child_bfc_offset.block_offset, logical_offset, *layout_result, fragment, - /* empty_block_affected_by_clearance */ false); + /* self_collapsing_child_had_clearance */ false); return true; } @@ -1192,12 +1173,12 @@ NGBlockLayoutAlgorithm::LayoutNewFormattingContext( // auto inline size by subtracting the inline margins from available inline // size. We have calculated a layout opportunity without margins in mind, // since they overlap with adjacent floats. Now we need to add them. - NGLogicalSize child_available_size = { + LogicalSize child_available_size = { (opportunity.rect.InlineSize() - inline_negative_margin + inline_margin) .ClampNegativeToZero(), child_available_size_.block_size}; - NGConstraintSpace child_space = - CreateConstraintSpaceForChild(child, child_data, child_available_size); + NGConstraintSpace child_space = CreateConstraintSpaceForChild( + child, child_data, child_available_size, /* is_new_fc */ true); // All formatting context roots (like this child) should start with an empty // exclusion space. @@ -1210,9 +1191,8 @@ NGBlockLayoutAlgorithm::LayoutNewFormattingContext( // should be returned. DCHECK(layout_result->ExclusionSpace().IsEmpty()); - DCHECK(layout_result->PhysicalFragment()); NGFragment fragment(ConstraintSpace().GetWritingMode(), - *layout_result->PhysicalFragment()); + layout_result->PhysicalFragment()); // Now we can check if the fragment will fit in this layout opportunity. if ((opportunity.rect.InlineSize() >= fragment.InlineSize() || @@ -1237,7 +1217,8 @@ bool NGBlockLayoutAlgorithm::HandleInflow( DCHECK(!child.CreatesNewFormattingContext()); auto* child_inline_node = DynamicTo<NGInlineNode>(child); - if (child_inline_node && !child_break_token) { + if (child_inline_node && !child_break_token && + RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) { DCHECK(!*previous_inline_break_token); bool aborted = false; *previous_inline_break_token = TryReuseFragmentsFromCache( @@ -1251,10 +1232,12 @@ bool NGBlockLayoutAlgorithm::HandleInflow( bool is_non_empty_inline = child_inline_node && !child_inline_node->IsEmptyInline(); bool has_clearance_past_adjoining_floats = - child.IsBlock() && + !container_builder_.BfcBlockOffset() && child.IsBlock() && HasClearancePastAdjoiningFloats(container_builder_.AdjoiningFloatTypes(), child.Style(), Style()); + base::Optional<LayoutUnit> forced_bfc_block_offset; + // If we can separate the previous margin strut from what is to follow, do // that. Then we're able to resolve *our* BFC block offset and position any // pending floats. There are two situations where this is necessary: @@ -1266,21 +1249,33 @@ bool NGBlockLayoutAlgorithm::HandleInflow( if (has_clearance_past_adjoining_floats || is_non_empty_inline) { if (!ResolveBfcBlockOffset(previous_inflow_position)) return false; + + // If we had clearance past any adjoining floats, we already know where the + // child is going to be (the child's margins won't have any effect). + // + // Set the forced BFC block-offset to the appropriate clearance offset to + // force this placement of this child. + if (has_clearance_past_adjoining_floats) { + forced_bfc_block_offset = exclusion_space_.ClearanceOffset( + ResolvedClear(child.Style(), Style())); + } } // Perform layout on the child. - NGInflowChildData child_data = ComputeChildData( - *previous_inflow_position, child, child_break_token, - has_clearance_past_adjoining_floats, /* is_new_fc */ false); - NGConstraintSpace child_space = - CreateConstraintSpaceForChild(child, child_data, child_available_size_); - scoped_refptr<const NGLayoutResult> layout_result = - child.Layout(child_space, child_break_token, inline_child_layout_context); + NGInflowChildData child_data = + ComputeChildData(*previous_inflow_position, child, child_break_token, + /* is_new_fc */ false); + NGConstraintSpace child_space = CreateConstraintSpaceForChild( + child, child_data, child_available_size_, /* is_new_fc */ false, + forced_bfc_block_offset, has_clearance_past_adjoining_floats); + scoped_refptr<const NGLayoutResult> layout_result = LayoutInflow( + child_space, child_break_token, &child, inline_child_layout_context); // To save space of the stack when we recurse into |NGBlockNode::Layout| // above, the rest of this function is continued within |FinishInflow|. // However it should be read as one function. return FinishInflow(child, child_break_token, child_space, + has_clearance_past_adjoining_floats, std::move(layout_result), &child_data, previous_inflow_position, inline_child_layout_context, previous_inline_break_token); @@ -1290,6 +1285,7 @@ bool NGBlockLayoutAlgorithm::FinishInflow( NGLayoutInputNode child, const NGBreakToken* child_break_token, const NGConstraintSpace& child_space, + bool has_clearance_past_adjoining_floats, scoped_refptr<const NGLayoutResult> layout_result, NGInflowChildData* child_data, NGPreviousInflowPosition* previous_inflow_position, @@ -1297,59 +1293,76 @@ bool NGBlockLayoutAlgorithm::FinishInflow( scoped_refptr<const NGInlineBreakToken>* previous_inline_break_token) { base::Optional<LayoutUnit> child_bfc_block_offset = layout_result->BfcBlockOffset(); - // TODO(layout-dev): A more optimal version of this is to set - // relayout_child_when_bfc_resolved only if the child tree itself _added_ any - // floats that it failed to position. Currently, we risk relaying out the - // parent block for no reason, because we're not able to make this - // distinction. - bool relayout_child_when_bfc_resolved = - layout_result->AdjoiningFloatTypes() && !child_bfc_block_offset && - !child_space.FloatsBfcBlockOffset(); - bool is_empty_block = IsEmptyBlock(child_space, *layout_result); - - bool has_clearance = layout_result->IsPushedByFloats(); - - // A child may have aborted its layout if it resolved its BFC block offset. - // If we don't have a BFC block offset yet, we need to propagate the abortion - // up to our parent. + + bool is_self_collapsing = layout_result->IsSelfCollapsing(); + + // Only non self-collapsing children (e.g. "normal children") can be pushed + // by floats in this way. + bool normal_child_had_clearance = layout_result->IsPushedByFloats(); + DCHECK(!normal_child_had_clearance || !is_self_collapsing); + + // A child may have aborted its layout if it resolved its BFC block-offset. + // If we don't have a BFC block-offset yet, we need to propagate the abort + // signal up to our parent. if (layout_result->Status() == NGLayoutResult::kBfcBlockOffsetResolved && !container_builder_.BfcBlockOffset()) { - // There's no need to do anything apart from resolving the BFC block offset + // There's no need to do anything apart from resolving the BFC block-offset // here, so make sure that it aborts before trying to position floats or - // anything like that, which would just be waste of time. This is simply - // propagating an abort up to a node which is able to restart the layout (a - // node that has resolved its BFC block offset). + // anything like that, which would just be waste of time. + // + // This is simply propagating an abort up to a node which is able to + // restart the layout (a node that has resolved its BFC block-offset). DCHECK(child_bfc_block_offset); abort_when_bfc_block_offset_updated_ = true; - ResolveBfcBlockOffset(previous_inflow_position, - has_clearance - ? NextBorderEdge(*previous_inflow_position) - : *child_bfc_block_offset); - return false; + + LayoutUnit bfc_block_offset = *child_bfc_block_offset; + + if (normal_child_had_clearance) { + // If the child has the same clearance-offset as ourselves it means that + // we should *also* resolve ourselves at that offset, (and we also have + // been pushed by floats). + if (ConstraintSpace().ClearanceOffset() == child_space.ClearanceOffset()) + container_builder_.SetIsPushedByFloats(); + else + bfc_block_offset = NextBorderEdge(*previous_inflow_position); + } + + // A new formatting-context may have previously tried to resolve the BFC + // block-offset. In this case we'll have a "forced" BFC block-offset + // present, but we shouldn't apply it (instead preferring the child's new + // BFC block-offset). + DCHECK(!ConstraintSpace().AncestorHasClearancePastAdjoiningFloats()); + if (!ResolveBfcBlockOffset(previous_inflow_position, bfc_block_offset, + /* forced_bfc_block_offset */ base::nullopt)) + return false; } - // We have special behaviour for an empty block which gets pushed down due to - // clearance, see comment inside ComputeInflowPosition. - bool empty_block_affected_by_clearance = false; + // We have special behaviour for a self-collapsing child which gets pushed + // down due to clearance, see comment inside |ComputeInflowPosition|. + bool self_collapsing_child_had_clearance = + is_self_collapsing && has_clearance_past_adjoining_floats; - // We try and position the child within the block formatting context. This - // may cause our BFC block offset to be resolved, in which case we should + // We try and position the child within the block formatting-context. This + // may cause our BFC block-offset to be resolved, in which case we should // abort our layout if needed. if (!child_bfc_block_offset) { - if (!has_clearance && child_space.HasClearanceOffset() && + DCHECK(is_self_collapsing); + if (child_space.HasClearanceOffset() && child.Style().Clear() != EClear::kNone) { - // This is an empty block child that we collapsed through, so we have to - // detect clearance manually. See if the child's hypothetical border edge - // is past the relevant floats. If it's not, we need to apply clearance - // before it. + // This is a self-collapsing child that we collapsed through, so we have + // to detect clearance manually. See if the child's hypothetical border + // edge is past the relevant floats. If it's not, we need to apply + // clearance before it. LayoutUnit child_block_offset_estimate = BfcBlockOffset() + layout_result->EndMarginStrut().Sum(); - if (child_block_offset_estimate < child_space.ClearanceOffset() || - child_space.ShouldForceClearance()) - has_clearance = empty_block_affected_by_clearance = true; + if (child_block_offset_estimate < child_space.ClearanceOffset()) + self_collapsing_child_had_clearance = true; } } - if (has_clearance) { + + bool child_had_clearance = + self_collapsing_child_had_clearance || normal_child_had_clearance; + if (child_had_clearance) { // The child has clearance. Clearance inhibits margin collapsing and acts as // spacing before the block-start margin of the child. Our BFC block offset // is therefore resolvable, and if it hasn't already been resolved, we'll @@ -1357,31 +1370,50 @@ bool NGBlockLayoutAlgorithm::FinishInflow( if (!ResolveBfcBlockOffset(previous_inflow_position)) return false; } + + bool self_collapsing_child_needs_relayout = false; if (!child_bfc_block_offset) { - DCHECK(is_empty_block); - // Layout wasn't able to determine the BFC block offset of the child. This - // has to mean that the child is empty (block-size-wise). + // Layout wasn't able to determine the BFC block-offset of the child. This + // has to mean that the child is self-collapsing. + DCHECK(is_self_collapsing); + if (container_builder_.BfcBlockOffset()) { - // Since we know our own BFC block offset, though, we can calculate that + // Since we know our own BFC block-offset, though, we can calculate that // of the child as well. - child_bfc_block_offset = PositionEmptyChildWithParentBfc( + child_bfc_block_offset = PositionSelfCollapsingChildWithParentBfc( child, child_space, *child_data, *layout_result); + + // We may need to relayout this child if it had any objects which were + // positioned in the incorrect position. + // + // TODO(layout-dev): A more optimal version of this is to set this flag + // only if the child tree *added* any floats which it failed to position. + // Currently, we risk relaying out the sub-tree for no reason, because + // we're not able to make this distinction. + if (layout_result->AdjoiningFloatTypes() && + !child_space.ForcedBfcBlockOffset()) + self_collapsing_child_needs_relayout = true; } - } else if (!has_clearance) { + } else if (!child_had_clearance) { // We shouldn't have any pending floats here, since an in-flow child found // its BFC block offset. DCHECK(unpositioned_floats_.IsEmpty()); - // The child's BFC block offset is known, and since there's no clearance, - // this container will get the same offset, unless it has already been - // resolved. - if (!ResolveBfcBlockOffset(previous_inflow_position, - *child_bfc_block_offset)) - return false; + // Only non self-collapsing children are allowed resolve the BFC + // block-offset. We check the BFC block-offset at the end of layout + // determine if this fragment is self-collapsing. + if (!is_self_collapsing) { + // The child's BFC block offset is known, and since there's no clearance, + // this container will get the same offset, unless it has already been + // resolved. + if (!ResolveBfcBlockOffset(previous_inflow_position, + *child_bfc_block_offset)) + return false; + } } - // We need to re-layout a child if it was affected by clearance in order to - // produce a new margin strut. For example: + // We need to re-layout a self-collapsing child if it was affected by + // clearance in order to produce a new margin strut. For example: // <div style="margin-bottom: 50px;"></div> // <div id="float" style="height: 50px;"></div> // <div id="zero" style="clear: left; margin-top: -20px;"> @@ -1395,9 +1427,8 @@ bool NGBlockLayoutAlgorithm::FinishInflow( // relayout with an empty incoming margin strut. // // The resulting margin strut in the above example will be {40, -30}. See - // ComputeInflowPosition for how this end margin strut is used. - bool empty_block_affected_by_clearance_needs_relayout = false; - if (empty_block_affected_by_clearance) { + // |ComputeInflowPosition| for how this end margin strut is used. + if (self_collapsing_child_had_clearance) { NGMarginStrut margin_strut; margin_strut.Append(child_data->margins.block_start, child.Style().HasMarginBeforeQuirk()); @@ -1406,7 +1437,7 @@ bool NGBlockLayoutAlgorithm::FinishInflow( // previous one. if (child_data->margin_strut != margin_strut) { child_data->margin_strut = margin_strut; - empty_block_affected_by_clearance_needs_relayout = true; + self_collapsing_child_needs_relayout = true; } } @@ -1415,12 +1446,12 @@ bool NGBlockLayoutAlgorithm::FinishInflow( // - It has some unpositioned floats. // - It was affected by clearance. if ((layout_result->Status() == NGLayoutResult::kBfcBlockOffsetResolved || - relayout_child_when_bfc_resolved || - empty_block_affected_by_clearance_needs_relayout) && + self_collapsing_child_needs_relayout) && child_bfc_block_offset) { NGConstraintSpace new_child_space = CreateConstraintSpaceForChild( - child, *child_data, child_available_size_, child_bfc_block_offset); - layout_result = child.Layout(new_child_space, child_break_token, + child, *child_data, child_available_size_, /* is_new_fc */ false, + child_bfc_block_offset); + layout_result = LayoutInflow(new_child_space, child_break_token, &child, inline_child_layout_context); if (layout_result->Status() == NGLayoutResult::kBfcBlockOffsetResolved) { @@ -1432,45 +1463,51 @@ bool NGBlockLayoutAlgorithm::FinishInflow( child_bfc_block_offset = layout_result->BfcBlockOffset(); DCHECK(child_bfc_block_offset); new_child_space = CreateConstraintSpaceForChild( - child, *child_data, child_available_size_, child_bfc_block_offset); - layout_result = child.Layout(new_child_space, child_break_token, + child, *child_data, child_available_size_, /* is_new_fc */ false, + child_bfc_block_offset); + layout_result = LayoutInflow(new_child_space, child_break_token, &child, inline_child_layout_context); } DCHECK_EQ(layout_result->Status(), NGLayoutResult::kSuccess); - relayout_child_when_bfc_resolved = false; } - // It is now safe to update our version of the exclusion space. + // It is now safe to update our version of the exclusion space, and any + // propagated adjoining floats. exclusion_space_ = layout_result->ExclusionSpace(); - // If we don't know our BFC block offset yet, and the child stumbled into - // something that needs it (unable to position floats when the BFC block - // offset is unknown), we need abort layout once we manage to resolve it, and - // relayout. Note that this check is performed after the optional second - // layout pass above, since we may have been able to resolve our BFC block - // offset (e.g. due to clearance) and position any descendant floats in the - // second pass. In particular, when it comes to clearance of empty blocks, if - // we just applied it and resolved the BFC block offset to separate the + // Only self-collapsing children should have adjoining floats. + DCHECK(!layout_result->AdjoiningFloatTypes() || is_self_collapsing); + container_builder_.SetAdjoiningFloatTypes( + layout_result->AdjoiningFloatTypes()); + + // If we don't know our BFC block-offset yet, and the child stumbled into + // something that needs it (unable to position floats yet), we need abort + // layout, and trigger a re-layout once we manage to resolve it. + // + // NOTE: This check is performed after the optional second layout pass above, + // since we may have been able to resolve our BFC block-offset (e.g. due to + // clearance) and position any descendant floats in the second pass. + // In particular, when it comes to clearance of self-collapsing children, if + // we just applied it and resolved the BFC block-offset to separate the // margins before and after clearance, we cannot abort and re-layout this - // block, or clearance would be lost. + // child, or clearance would be lost. // // If we are a new formatting context, the child will get re-laid out once it // has been positioned. if (!container_builder_.BfcBlockOffset()) { - abort_when_bfc_block_offset_updated_ |= relayout_child_when_bfc_resolved; + abort_when_bfc_block_offset_updated_ |= + layout_result->AdjoiningFloatTypes(); // If our BFC block offset is unknown, and the child got pushed down by // floats, so will we. if (layout_result->IsPushedByFloats()) container_builder_.SetIsPushedByFloats(); } - // We must have an actual fragment at this stage. - DCHECK(layout_result->PhysicalFragment()); - const auto& physical_fragment = *layout_result->PhysicalFragment(); + const auto& physical_fragment = layout_result->PhysicalFragment(); NGFragment fragment(ConstraintSpace().GetWritingMode(), physical_fragment); - NGLogicalOffset logical_offset = CalculateLogicalOffset( + LogicalOffset logical_offset = CalculateLogicalOffset( fragment, layout_result->BfcLineOffset(), child_bfc_block_offset); if (ConstraintSpace().HasBlockFragmentation()) { @@ -1485,12 +1522,7 @@ bool NGBlockLayoutAlgorithm::FinishInflow( PositionOrPropagateListMarker(*layout_result, &logical_offset); - if (is_empty_block && !container_builder_.BfcBlockOffset()) { - container_builder_.AddAdjoiningFloatTypes( - layout_result->AdjoiningFloatTypes()); - } - - container_builder_.AddChild(*layout_result, logical_offset); + container_builder_.AddChild(physical_fragment, logical_offset); if (child.IsBlock()) container_builder_.PropagateBreak(*layout_result); @@ -1512,11 +1544,10 @@ bool NGBlockLayoutAlgorithm::FinishInflow( *previous_inflow_position = ComputeInflowPosition( *previous_inflow_position, child, *child_data, child_bfc_block_offset, logical_offset, *layout_result, fragment, - empty_block_affected_by_clearance); + self_collapsing_child_had_clearance); *previous_inline_break_token = - child.IsInline() ? To<NGInlineBreakToken>( - layout_result->PhysicalFragment()->BreakToken()) + child.IsInline() ? To<NGInlineBreakToken>(physical_fragment.BreakToken()) : nullptr; return true; @@ -1526,7 +1557,6 @@ NGInflowChildData NGBlockLayoutAlgorithm::ComputeChildData( const NGPreviousInflowPosition& previous_inflow_position, NGLayoutInputNode child, const NGBreakToken* child_break_token, - bool force_clearance, bool is_new_fc) { DCHECK(child); DCHECK(!child.IsFloating()); @@ -1538,8 +1568,8 @@ NGInflowChildData NGBlockLayoutAlgorithm::ComputeChildData( &margins_fully_resolved); // Append the current margin strut with child's block start margin. - // Non empty border/padding, and new FC use cases are handled inside of the - // child's layout + // Non empty border/padding, and new formatting-context use cases are handled + // inside of the child's layout NGMarginStrut margin_strut = previous_inflow_position.margin_strut; LayoutUnit logical_block_offset = @@ -1561,8 +1591,7 @@ NGInflowChildData NGBlockLayoutAlgorithm::ComputeChildData( margins.LineLeft(ConstraintSpace().Direction()), BfcBlockOffset() + logical_block_offset}; - return {child_bfc_offset, margin_strut, margins, - margins_fully_resolved, force_clearance, is_new_fc}; + return {child_bfc_offset, margin_strut, margins, margins_fully_resolved}; } NGPreviousInflowPosition NGBlockLayoutAlgorithm::ComputeInflowPosition( @@ -1570,53 +1599,54 @@ NGPreviousInflowPosition NGBlockLayoutAlgorithm::ComputeInflowPosition( const NGLayoutInputNode child, const NGInflowChildData& child_data, const base::Optional<LayoutUnit>& child_bfc_block_offset, - const NGLogicalOffset& logical_offset, + const LogicalOffset& logical_offset, const NGLayoutResult& layout_result, const NGFragment& fragment, - bool empty_block_affected_by_clearance) { + bool self_collapsing_child_had_clearance) { // Determine the child's end logical offset, for the next child to use. LayoutUnit logical_block_offset; - bool is_empty_block = IsEmptyBlock(child_data.is_new_fc, layout_result); - if (is_empty_block) { - // The default behaviour for empty blocks is they just pass through the - // previous inflow position. + bool is_self_collapsing = layout_result.IsSelfCollapsing(); + if (is_self_collapsing) { + // The default behaviour for self-collapsing children is they just pass + // through the previous inflow position. logical_block_offset = previous_inflow_position.logical_block_offset; - if (empty_block_affected_by_clearance) { + if (self_collapsing_child_had_clearance) { // If there's clearance, we must have applied that by now and thus - // resolved our BFC block offset. + // resolved our BFC block-offset. DCHECK(container_builder_.BfcBlockOffset()); DCHECK(child_bfc_block_offset.has_value()); - // If an empty block was affected by clearance (that is it got pushed - // down past a float), we need to do something slightly bizarre. + // If a self-collapsing child was affected by clearance (that is it got + // pushed down past a float), we need to do something slightly bizarre. // // Instead of just passing through the previous inflow position, we make // the inflow position our new position (which was affected by the - // float), minus what the margin strut which the empty block produced. + // float), minus what the margin strut which the self-collapsing child + // produced. // // Another way of thinking about this is that when you *add* back the // margin strut, you end up with the same position as you started with. // // This is essentially what the spec refers to as clearance [1], and, // while we normally don't have to calculate it directly, in the case of - // an empty cleared child like here, we actually have to. + // a self-collapsing cleared child like here, we actually have to. // - // We have to calculate clearance for empty cleared children, because we - // need the margin that's between the clearance and this block to collapse - // correctly with subsequent content. This is something that needs to take - // place after the margin strut preceding and following the clearance have - // been separated. Clearance may be positive, negative or zero, depending - // on what it takes to (hypothetically) place this child just below the - // last relevant float. Since the margins before and after the clearance - // have been separated, we may have to pull the child back, and that's an - // example of negative clearance. + // We have to calculate clearance for self-collapsing cleared children, + // because we need the margin that's between the clearance and this block + // to collapse correctly with subsequent content. This is something that + // needs to take place after the margin strut preceding and following the + // clearance have been separated. Clearance may be positive, negative or + // zero, depending on what it takes to (hypothetically) place this child + // just below the last relevant float. Since the margins before and after + // the clearance have been separated, we may have to pull the child back, + // and that's an example of negative clearance. // - // (In the other case, when a cleared child is non-empty (i.e. when we - // don't end up here), we don't need to explicitly calculate clearance, - // because then we just place its border edge where it should be and we're - // done with it.) + // (In the other case, when a cleared child is non self-collapsing (i.e. + // when we don't end up here), we don't need to explicitly calculate + // clearance, because then we just place its border edge where it should + // be and we're done with it.) // // [1] https://www.w3.org/TR/CSS22/visuren.html#flow-control @@ -1646,17 +1676,19 @@ NGPreviousInflowPosition NGBlockLayoutAlgorithm::ComputeInflowPosition( child_data.margins.block_end, &logical_block_offset, &margin_strut); } else { - // An empty block's end margin can "inherit" quirkiness from its start - // margin. E.g. + // Self collapsing child's end margin can "inherit" quirkiness from its + // start margin. E.g. // <ol style="margin-bottom: 20px"></ol> - bool is_quirky = (is_empty_block && child.Style().HasMarginBeforeQuirk()) || - child.Style().HasMarginAfterQuirk(); + bool is_quirky = + (is_self_collapsing && child.Style().HasMarginBeforeQuirk()) || + child.Style().HasMarginAfterQuirk(); margin_strut.Append(child_data.margins.block_end, is_quirky); } // This flag is subtle, but in order to determine our size correctly we need - // to check if our last child is an empty block, and it was affected by - // clearance *or* an adjoining empty sibling was affected by clearance. E.g. + // to check if our last child is self-collapsing, and it was affected by + // clearance *or* an adjoining self-collapsing sibling was affected by + // clearance. E.g. // <div id="container"> // <div id="float"></div> // <div id="zero-with-clearance"></div> @@ -1664,21 +1696,21 @@ NGPreviousInflowPosition NGBlockLayoutAlgorithm::ComputeInflowPosition( // </div> // In the above case #container's size will depend on the end margin strut of // #another-zero, even though usually it wouldn't. - bool empty_or_sibling_empty_affected_by_clearance = - empty_block_affected_by_clearance || - (previous_inflow_position.empty_block_affected_by_clearance && - is_empty_block); + bool self_or_sibling_self_collapsing_child_had_clearance = + self_collapsing_child_had_clearance || + (previous_inflow_position.self_collapsing_child_had_clearance && + is_self_collapsing); return {logical_block_offset, margin_strut, - empty_or_sibling_empty_affected_by_clearance}; + self_or_sibling_self_collapsing_child_had_clearance}; } -LayoutUnit NGBlockLayoutAlgorithm::PositionEmptyChildWithParentBfc( +LayoutUnit NGBlockLayoutAlgorithm::PositionSelfCollapsingChildWithParentBfc( const NGLayoutInputNode& child, const NGConstraintSpace& child_space, const NGInflowChildData& child_data, const NGLayoutResult& layout_result) const { - DCHECK(IsEmptyBlock(child_space, layout_result)); + DCHECK(layout_result.IsSelfCollapsing()); // The child must be an in-flow zero-block-size fragment, use its end margin // strut for positioning. @@ -1719,7 +1751,7 @@ void NGBlockLayoutAlgorithm::FinalizeForFragmentation() { LayoutUnit used_block_size = BreakToken() ? BreakToken()->UsedBlockSize() : LayoutUnit(); LayoutUnit block_size = - ComputeBlockSizeForFragment(ConstraintSpace(), Style(), border_padding_, + ComputeBlockSizeForFragment(ConstraintSpace(), Node(), border_padding_, used_block_size + intrinsic_block_size_); block_size -= used_block_size; @@ -1802,7 +1834,7 @@ bool NGBlockLayoutAlgorithm::BreakBeforeChild( // Calculate space shortage: Figure out how much more space would have been // sufficient to make the child fit right here in the current fragment. NGFragment fragment(ConstraintSpace().GetWritingMode(), - *layout_result.PhysicalFragment()); + layout_result.PhysicalFragment()); LayoutUnit space_left = space_available - block_offset; space_shortage = fragment.BlockSize() - space_left; } else { @@ -1925,8 +1957,8 @@ NGBlockLayoutAlgorithm::BreakType NGBlockLayoutAlgorithm::BreakTypeBeforeChild( if (!container_builder_.BfcBlockOffset().has_value()) return NoBreak; - const NGPhysicalFragment& physical_fragment = - *layout_result.PhysicalFragment(); + const NGPhysicalContainerFragment& physical_fragment = + layout_result.PhysicalFragment(); // If we haven't used any space at all in the fragmentainer yet, we cannot // break, or there'd be no progress. We'd end up creating an infinite number @@ -2050,15 +2082,17 @@ NGBoxStrut NGBlockLayoutAlgorithm::CalculateMargins( NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( const NGLayoutInputNode child, const NGInflowChildData& child_data, - const NGLogicalSize child_available_size, - const base::Optional<LayoutUnit> floats_bfc_block_offset) { + const LogicalSize child_available_size, + bool is_new_fc, + const base::Optional<LayoutUnit> forced_bfc_block_offset, + bool has_clearance_past_adjoining_floats) { const ComputedStyle& style = Style(); const ComputedStyle& child_style = child.Style(); WritingMode child_writing_mode = child.IsInline() ? style.GetWritingMode() : child_style.GetWritingMode(); NGConstraintSpaceBuilder builder(ConstraintSpace(), child_writing_mode, - child_data.is_new_fc); + is_new_fc); SetOrthogonalFallbackInlineSizeIfNeeded(Style(), child, &builder); if (!IsParallelWritingMode(ConstraintSpace().GetWritingMode(), @@ -2075,6 +2109,9 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( ConstraintSpace().IsFixedSizeBlock() ? NGTableCellChildLayoutPhase::kLayout : NGTableCellChildLayoutPhase::kMeasure); + + if (Node().IsRestrictedBlockSizeTableCell()) + builder.SetIsInRestrictedBlockSizeTableCell(); } if (NGBaseline::ShouldPropagateBaselines(child)) @@ -2083,13 +2120,22 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( builder.SetBfcOffset(child_data.bfc_offset_estimate) .SetMarginStrut(child_data.margin_strut); - if (!container_builder_.BfcBlockOffset() && - ConstraintSpace().FloatsBfcBlockOffset()) { - builder.SetFloatsBfcBlockOffset(*ConstraintSpace().FloatsBfcBlockOffset()); - } + bool has_bfc_block_offset = container_builder_.BfcBlockOffset().has_value(); - if (floats_bfc_block_offset) - builder.SetFloatsBfcBlockOffset(floats_bfc_block_offset); + // Propagate the |NGConstraintSpace::ForcedBfcBlockOffset| down to our + // children. + if (!has_bfc_block_offset && ConstraintSpace().ForcedBfcBlockOffset()) + builder.SetForcedBfcBlockOffset(*ConstraintSpace().ForcedBfcBlockOffset()); + if (forced_bfc_block_offset) + builder.SetForcedBfcBlockOffset(forced_bfc_block_offset); + + // Propagate the |NGConstraintSpace::AncestorHasClearancePastAdjoiningFloats| + // flag down to our children. + if (!has_bfc_block_offset && + ConstraintSpace().AncestorHasClearancePastAdjoiningFloats()) + builder.SetAncestorHasClearancePastAdjoiningFloats(); + if (has_clearance_past_adjoining_floats) + builder.SetAncestorHasClearancePastAdjoiningFloats(); LayoutUnit clearance_offset = ConstraintSpace().IsNewFormattingContext() ? LayoutUnit::Min() @@ -2109,12 +2155,11 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( builder.SetTextDirection(style.Direction()); } builder.SetClearanceOffset(clearance_offset); - if (child_data.force_clearance) - builder.SetShouldForceClearance(true); - if (!child_data.is_new_fc) { + if (!is_new_fc) { builder.SetExclusionSpace(exclusion_space_); - builder.SetAdjoiningFloatTypes(container_builder_.AdjoiningFloatTypes()); + if (!has_bfc_block_offset) + builder.SetAdjoiningFloatTypes(container_builder_.AdjoiningFloatTypes()); } LayoutUnit space_available; @@ -2123,9 +2168,9 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( // If a block establishes a new formatting context we must know our // position in the formatting context, and are able to adjust the // fragmentation line. - if (child_data.is_new_fc) { + if (is_new_fc) space_available -= child_data.bfc_offset_estimate.block_offset; - } + // The policy regarding collapsing block-start margin with the fragmentainer // block-start is the same throughout the entire fragmentainer (although it // really only matters at the beginning of each fragmentainer, we don't need @@ -2172,23 +2217,23 @@ LayoutUnit NGBlockLayoutAlgorithm::ComputeLineBoxBaselineOffset( // Add a baseline from a child box fragment. // @return false if the specified child is not a box or is OOF. bool NGBlockLayoutAlgorithm::AddBaseline(const NGBaselineRequest& request, - const NGPhysicalFragment* child, + const NGPhysicalFragment& child, LayoutUnit child_offset) { - if (child->IsLineBox()) { - const auto* line_box = To<NGPhysicalLineBoxFragment>(child); + if (child.IsLineBox()) { + const auto& line_box = To<NGPhysicalLineBoxFragment>(child); // Skip over a line-box which is empty. These don't have any baselines which // should be added. - if (line_box->IsEmptyLineBox()) + if (line_box.IsEmptyLineBox()) return false; LayoutUnit offset = - ComputeLineBoxBaselineOffset(request, *line_box, child_offset); + ComputeLineBoxBaselineOffset(request, line_box, child_offset); container_builder_.AddBaseline(request, offset); return true; } - if (child->IsFloatingOrOutOfFlowPositioned()) + if (child.IsFloatingOrOutOfFlowPositioned()) return false; if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(child)) { @@ -2210,21 +2255,20 @@ void NGBlockLayoutAlgorithm::PropagateBaselinesFromChildren() { for (const auto& request : requests) { switch (request.AlgorithmType()) { - case NGBaselineAlgorithmType::kAtomicInline: + case NGBaselineAlgorithmType::kAtomicInline: { if (Node().UseLogicalBottomMarginEdgeForInlineBlockBaseline()) break; - for (unsigned i = container_builder_.Children().size(); i--;) { - if (AddBaseline(request, container_builder_.Children()[i].get(), - container_builder_.Offsets()[i].block_offset)) + const auto& children = container_builder_.Children(); + for (auto it = children.rbegin(); it != children.rend(); ++it) { + if (AddBaseline(request, *it->fragment, it->offset.block_offset)) break; } break; - + } case NGBaselineAlgorithmType::kFirstLine: - for (unsigned i = 0; i < container_builder_.Children().size(); i++) { - if (AddBaseline(request, container_builder_.Children()[i].get(), - container_builder_.Offsets()[i].block_offset)) + for (const auto& child : container_builder_.Children()) { + if (AddBaseline(request, *child.fragment, child.offset.block_offset)) break; } break; @@ -2234,17 +2278,19 @@ void NGBlockLayoutAlgorithm::PropagateBaselinesFromChildren() { bool NGBlockLayoutAlgorithm::ResolveBfcBlockOffset( NGPreviousInflowPosition* previous_inflow_position, - LayoutUnit bfc_block_offset) { + LayoutUnit bfc_block_offset, + base::Optional<LayoutUnit> forced_bfc_block_offset) { if (container_builder_.BfcBlockOffset()) { DCHECK(unpositioned_floats_.IsEmpty()); return true; } + bfc_block_offset = forced_bfc_block_offset.value_or(bfc_block_offset); + if (ApplyClearance(ConstraintSpace(), &bfc_block_offset)) container_builder_.SetIsPushedByFloats(); container_builder_.SetBfcBlockOffset(bfc_block_offset); - container_builder_.ResetAdjoiningFloatTypes(); if (NeedsAbortOnBfcBlockOffsetChange()) return false; @@ -2274,22 +2320,19 @@ bool NGBlockLayoutAlgorithm::NeedsAbortOnBfcBlockOffsetChange() const { if (!abort_when_bfc_block_offset_updated_) return false; // If no previous BFC block offset was set, we need to abort. - if (!ConstraintSpace().FloatsBfcBlockOffset()) + if (!ConstraintSpace().ForcedBfcBlockOffset()) return true; + // If the previous BFC block offset matches the new one, we can continue. // Otherwise, we need to abort. - LayoutUnit old_bfc_block_offset = *ConstraintSpace().FloatsBfcBlockOffset(); - - // In order to determine if the two offsets are equal, we also need to adjust - // floats offset by the clearance offset. - ApplyClearance(ConstraintSpace(), &old_bfc_block_offset); - return *container_builder_.BfcBlockOffset() != old_bfc_block_offset; + return *container_builder_.BfcBlockOffset() != + *ConstraintSpace().ForcedBfcBlockOffset(); } void NGBlockLayoutAlgorithm::PositionPendingFloats( LayoutUnit origin_block_offset) { DCHECK(container_builder_.BfcBlockOffset() || - ConstraintSpace().FloatsBfcBlockOffset()) + ConstraintSpace().ForcedBfcBlockOffset()) << "The parent BFC block offset should be known here."; NGBfcOffset origin_bfc_offset = { @@ -2299,7 +2342,7 @@ void NGBlockLayoutAlgorithm::PositionPendingFloats( LayoutUnit bfc_block_offset = container_builder_.BfcBlockOffset() ? *container_builder_.BfcBlockOffset() - : *ConstraintSpace().FloatsBfcBlockOffset(); + : *ConstraintSpace().ForcedBfcBlockOffset(); NGBfcOffset bfc_offset = {ConstraintSpace().BfcOffset().line_offset, bfc_block_offset}; @@ -2310,16 +2353,17 @@ void NGBlockLayoutAlgorithm::PositionPendingFloats( replaced_child_percentage_size_, origin_bfc_offset, &unpositioned_float, ConstraintSpace(), Style(), &exclusion_space_); - NGFragment child_fragment( - ConstraintSpace().GetWritingMode(), - *positioned_float.layout_result->PhysicalFragment()); + const auto& physical_fragment = + positioned_float.layout_result->PhysicalFragment(); + LayoutUnit float_inline_size = + NGFragment(ConstraintSpace().GetWritingMode(), physical_fragment) + .InlineSize(); - NGLogicalOffset logical_offset = LogicalFromBfcOffsets( - positioned_float.bfc_offset, bfc_offset, child_fragment.InlineSize(), + LogicalOffset logical_offset = LogicalFromBfcOffsets( + positioned_float.bfc_offset, bfc_offset, float_inline_size, container_builder_.Size().inline_size, ConstraintSpace().Direction()); - container_builder_.AddChild(*positioned_float.layout_result, - logical_offset); + container_builder_.AddChild(physical_fragment, logical_offset); container_builder_.PropagateBreak(*positioned_float.layout_result); } @@ -2329,7 +2373,7 @@ void NGBlockLayoutAlgorithm::PositionPendingFloats( LayoutUnit NGBlockLayoutAlgorithm::CalculateMinimumBlockSize( const NGMarginStrut& end_margin_strut) { if (!Node().GetDocument().InQuirksMode()) - return NGSizeIndefinite; + return kIndefiniteSize; if (Node().IsDocumentElement() && Node().Style().LogicalHeight().IsAuto()) { return ConstraintSpace().AvailableSize().block_size - @@ -2353,12 +2397,12 @@ LayoutUnit NGBlockLayoutAlgorithm::CalculateMinimumBlockSize( ConstraintSpace().AvailableSize().block_size - margin_sum; return minimum_block_size.ClampNegativeToZero(); } - return NGSizeIndefinite; + return kIndefiniteSize; } void NGBlockLayoutAlgorithm::PositionOrPropagateListMarker( const NGLayoutResult& layout_result, - NGLogicalOffset* content_offset) { + LogicalOffset* content_offset) { // If this is not a list-item, propagate unpositioned list markers to // ancestors. if (!node_.IsListItem()) { @@ -2379,7 +2423,7 @@ void NGBlockLayoutAlgorithm::PositionOrPropagateListMarker( container_builder_.SetUnpositionedListMarker(NGUnpositionedListMarker()); } if (list_marker.AddToBox(ConstraintSpace(), Style().GetFontBaseline(), - *layout_result.PhysicalFragment(), content_offset, + layout_result.PhysicalFragment(), content_offset, &container_builder_, border_scrollbar_padding_)) return; @@ -2403,10 +2447,10 @@ void NGBlockLayoutAlgorithm::PositionListMarkerWithoutLineBoxes() { // well-defined, but 3 out of 4 impls do. // https://github.com/w3c/csswg-drafts/issues/2418 // - // TODO(kojii): Since this makes this block non-empty, it's probably better to - // resolve BFC block offset if not done yet, but that involves additional - // complexity without knowing how much this is needed. For now, include the - // marker into the block size only if BFC was resolved. + // TODO(kojii): Since this makes this block non self-collapsing, it's + // probably better to resolve BFC block-offset if not done yet, but that + // involves additional complexity without knowing how much this is needed. + // For now, include the marker into the block-size only if BFC was resolved. if (container_builder_.BfcBlockOffset()) { intrinsic_block_size_ = std::max(marker_block_size, intrinsic_block_size_); container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h index 07dffd9071e..70db5f9ab8a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h @@ -29,7 +29,7 @@ class NGPhysicalLineBoxFragment; struct NGPreviousInflowPosition { LayoutUnit logical_block_offset; NGMarginStrut margin_strut; - bool empty_block_affected_by_clearance; + bool self_collapsing_child_had_clearance; }; // This strut holds information for the current inflow child. The data is not @@ -39,8 +39,6 @@ struct NGInflowChildData { NGMarginStrut margin_strut; NGBoxStrut margins; bool margins_fully_resolved; - bool force_clearance; - bool is_new_fc; }; // A class for general block layout (e.g. a <div> with no special style). @@ -51,13 +49,7 @@ class CORE_EXPORT NGBlockLayoutAlgorithm NGBlockBreakToken> { public: // Default constructor. - // @param node The input node to perform layout upon. - // @param space The constraint space which the algorithm should generate a - // fragment within. - // @param break_token The break token from which the layout should start. - NGBlockLayoutAlgorithm(NGBlockNode node, - const NGConstraintSpace& space, - const NGBlockBreakToken* break_token = nullptr); + NGBlockLayoutAlgorithm(const NGLayoutAlgorithmParams& params); ~NGBlockLayoutAlgorithm() override; @@ -76,7 +68,7 @@ class CORE_EXPORT NGBlockLayoutAlgorithm scoped_refptr<const NGLayoutResult> FinishLayout( NGPreviousInflowPosition*, - NGLogicalSize border_box_size, + LogicalSize border_box_size, const NGBoxStrut& borders, const NGBoxStrut& scrollbars); @@ -107,14 +99,15 @@ class CORE_EXPORT NGBlockLayoutAlgorithm NGConstraintSpace CreateConstraintSpaceForChild( const NGLayoutInputNode child, const NGInflowChildData& child_data, - const NGLogicalSize child_available_size, - const base::Optional<LayoutUnit> floats_bfc_block_offset = base::nullopt); + 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 force_clearance, bool is_new_fc); NGPreviousInflowPosition ComputeInflowPosition( @@ -122,19 +115,19 @@ class CORE_EXPORT NGBlockLayoutAlgorithm const NGLayoutInputNode child, const NGInflowChildData&, const base::Optional<LayoutUnit>& child_bfc_block_offset, - const NGLogicalOffset&, + const LogicalOffset&, const NGLayoutResult&, const NGFragment&, - bool empty_block_affected_by_clearance); + bool self_collapsing_child_had_clearance); - // Position an empty child using the parent BFC block offset. + // 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="empty-div" style="margin: 1px"></div> - LayoutUnit PositionEmptyChildWithParentBfc( + // <div id="zero" style="margin: 1px"></div> + LayoutUnit PositionSelfCollapsingChildWithParentBfc( const NGLayoutInputNode& child, const NGConstraintSpace& child_space, const NGInflowChildData& child_data, @@ -198,6 +191,7 @@ class CORE_EXPORT NGBlockLayoutAlgorithm NGLayoutInputNode child, const NGBreakToken* child_break_token, const NGConstraintSpace&, + bool has_clearance_past_adjoining_floats, scoped_refptr<const NGLayoutResult>, NGInflowChildData*, NGPreviousInflowPosition*, @@ -239,7 +233,7 @@ class CORE_EXPORT NGBlockLayoutAlgorithm void PropagateBaselinesFromChildren(); bool AddBaseline(const NGBaselineRequest&, - const NGPhysicalFragment*, + const NGPhysicalFragment&, LayoutUnit child_offset); // Compute the baseline offset of a line box from the content box. @@ -252,18 +246,35 @@ class CORE_EXPORT NGBlockLayoutAlgorithm // 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(). + // 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); + // 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. @@ -281,7 +292,7 @@ class CORE_EXPORT NGBlockLayoutAlgorithm void PositionPendingFloats(LayoutUnit origin_block_offset); // Positions a list marker for the specified block content. - void PositionOrPropagateListMarker(const NGLayoutResult&, NGLogicalOffset*); + void PositionOrPropagateListMarker(const NGLayoutResult&, LogicalOffset*); // Positions a list marker when the block does not have any line boxes. void PositionListMarkerWithoutLineBoxes(); @@ -290,21 +301,22 @@ class CORE_EXPORT NGBlockLayoutAlgorithm // 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. - NGLogicalOffset CalculateLogicalOffset( + 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 NGSizeIndefinite in all other cases. + // Returns kIndefiniteSize in all other cases. LayoutUnit CalculateMinimumBlockSize(const NGMarginStrut& end_margin_strut); - NGLogicalSize child_available_size_; - NGLogicalSize child_percentage_size_; - NGLogicalSize replaced_child_percentage_size_; - - NGBoxStrut border_padding_; + 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 diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc index 94559f82244..986c3484c6f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc @@ -33,23 +33,16 @@ class NGBlockLayoutAlgorithmTest : public NGBaseLayoutAlgorithmTest { NGBaseLayoutAlgorithmTest::SetUp(); } - scoped_refptr<const NGPhysicalBoxFragment> RunBlockLayoutAlgorithm( - const NGConstraintSpace& space, - NGBlockNode node) { - scoped_refptr<const NGLayoutResult> result = - NGBlockLayoutAlgorithm(node, space).Layout(); - - return To<NGPhysicalBoxFragment>(result->PhysicalFragment()); - } - MinMaxSize RunComputeMinAndMax(NGBlockNode node) { // The constraint space is not used for min/max computation, but we need // it to create the algorithm. NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(), LayoutUnit())); + LogicalSize(LayoutUnit(), LayoutUnit())); + NGFragmentGeometry fragment_geometry = + CalculateInitialMinMaxFragmentGeometry(space, node); - NGBlockLayoutAlgorithm algorithm(node, space); + NGBlockLayoutAlgorithm algorithm({node, fragment_geometry, space}); MinMaxSizeInput input( /* percentage_resolution_block_size */ (LayoutUnit())); auto min_max = algorithm.ComputeMinMaxSize(input); @@ -57,6 +50,16 @@ class NGBlockLayoutAlgorithmTest : public NGBaseLayoutAlgorithmTest { return *min_max; } + 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, + &cache_status); + } + String DumpFragmentTree(const NGPhysicalBoxFragment* fragment) { NGPhysicalFragment::DumpFlags flags = NGPhysicalFragment::DumpHeaderText | NGPhysicalFragment::DumpSubtree | @@ -83,14 +86,14 @@ TEST_F(NGBlockLayoutAlgorithmTest, FixedSize) { NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), NGSizeIndefinite)); + LogicalSize(LayoutUnit(100), kIndefiniteSize)); NGBlockNode box(ToLayoutBox(GetLayoutObjectByElementId("box"))); - scoped_refptr<const NGPhysicalFragment> frag = - RunBlockLayoutAlgorithm(space, box); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(box, space); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(30), LayoutUnit(40)), frag->Size()); + EXPECT_EQ(PhysicalSize(30, 40), fragment->Size()); } TEST_F(NGBlockLayoutAlgorithmTest, Caching) { @@ -102,44 +105,43 @@ TEST_F(NGBlockLayoutAlgorithmTest, Caching) { NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), LayoutUnit(100))); + LogicalSize(LayoutUnit(100), LayoutUnit(100))); auto* block_flow = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box")); NGBlockNode node(block_flow); scoped_refptr<const NGLayoutResult> result(node.Layout(space, nullptr)); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(30), LayoutUnit(40)), - result->PhysicalFragment()->Size()); + EXPECT_EQ(PhysicalSize(30, 40), result->PhysicalFragment().Size()); // Test pointer-equal constraint space. - result = block_flow->CachedLayoutResult(space, nullptr); + result = RunCachedLayoutResult(space, node); EXPECT_NE(result.get(), nullptr); // Test identical, but not pointer-equal, constraint space. space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), LayoutUnit(100))); - result = block_flow->CachedLayoutResult(space, nullptr); + LogicalSize(LayoutUnit(100), LayoutUnit(100))); + result = RunCachedLayoutResult(space, node); EXPECT_NE(result.get(), nullptr); // Test different constraint space. space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(200), LayoutUnit(100))); - result = block_flow->CachedLayoutResult(space, nullptr); + LogicalSize(LayoutUnit(200), LayoutUnit(100))); + result = RunCachedLayoutResult(space, node); EXPECT_NE(result.get(), nullptr); // Test a different constraint space that will actually result in a different // size. space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(200), LayoutUnit(200))); - result = block_flow->CachedLayoutResult(space, nullptr); + LogicalSize(LayoutUnit(200), LayoutUnit(200))); + result = RunCachedLayoutResult(space, node); EXPECT_EQ(result.get(), nullptr); // Test layout invalidation block_flow->SetNeedsLayout(""); - result = block_flow->CachedLayoutResult(space, nullptr); + result = RunCachedLayoutResult(space, node); EXPECT_EQ(result.get(), nullptr); } @@ -152,39 +154,38 @@ TEST_F(NGBlockLayoutAlgorithmTest, MinInlineSizeCaching) { NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), LayoutUnit(100))); + LogicalSize(LayoutUnit(100), LayoutUnit(100))); auto* block_flow = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box")); NGBlockNode node(block_flow); scoped_refptr<const NGLayoutResult> result(node.Layout(space, nullptr)); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(30), LayoutUnit(40)), - result->PhysicalFragment()->Size()); + EXPECT_EQ(PhysicalSize(30, 40), result->PhysicalFragment().Size()); // Test pointer-equal constraint space. - result = block_flow->CachedLayoutResult(space, nullptr); + result = RunCachedLayoutResult(space, node); EXPECT_NE(result.get(), nullptr); // Test identical, but not pointer-equal, constraint space. space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), LayoutUnit(100))); - result = block_flow->CachedLayoutResult(space, nullptr); + LogicalSize(LayoutUnit(100), LayoutUnit(100))); + result = RunCachedLayoutResult(space, node); EXPECT_NE(result.get(), nullptr); // Test different constraint space. space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), LayoutUnit(200))); - result = block_flow->CachedLayoutResult(space, nullptr); + LogicalSize(LayoutUnit(100), LayoutUnit(200))); + result = RunCachedLayoutResult(space, node); EXPECT_NE(result.get(), nullptr); // Test a different constraint space that will actually result in a different // size. space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(200), LayoutUnit(100))); - result = block_flow->CachedLayoutResult(space, nullptr); + LogicalSize(LayoutUnit(200), LayoutUnit(100))); + result = RunCachedLayoutResult(space, node); EXPECT_EQ(result.get(), nullptr); } @@ -244,21 +245,22 @@ TEST_F(NGBlockLayoutAlgorithmTest, PercentageBlockSizeQuirkDescendantsCaching) { }; NGConstraintSpace space100 = - create_space(NGLogicalSize(LayoutUnit(100), LayoutUnit(100))); + create_space(LogicalSize(LayoutUnit(100), LayoutUnit(100))); NGConstraintSpace space200 = - create_space(NGLogicalSize(LayoutUnit(100), LayoutUnit(200))); + create_space(LogicalSize(LayoutUnit(100), LayoutUnit(200))); auto run_test = [&](auto id) -> scoped_refptr<const NGLayoutResult> { // Grab the box under test. auto* box = To<LayoutBlockFlow>(GetLayoutObjectByElementId(id)); + NGBlockNode node(box); // Check that we have a cache hit with space100. scoped_refptr<const NGLayoutResult> result = - box->CachedLayoutResult(space100, nullptr); + RunCachedLayoutResult(space100, node); EXPECT_NE(result.get(), nullptr); // Return the result of the cache with space200. - return box->CachedLayoutResult(space200, nullptr); + return RunCachedLayoutResult(space200, node); }; // Test 1: No descendants. @@ -309,8 +311,8 @@ TEST_F(NGBlockLayoutAlgorithmTest, ShrinkToFitCaching) { <div style="display: inline-block; width: 350px;"></div> <div style="display: inline-block; width: 250px;"></div> </div> - <div id="box3" style="float: left; min-width: 50%;"> - <div style="display: inline-block; width: 350px;"></div> + <div id="box3" style="float: left; min-width: 80%;"> + <div style="display: inline-block; width: 150px;"></div> <div style="display: inline-block; width: 250px;"></div> </div> <div id="box4" style="float: left; margin-left: 75px;"> @@ -322,23 +324,23 @@ TEST_F(NGBlockLayoutAlgorithmTest, ShrinkToFitCaching) { NGConstraintSpace space100 = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), LayoutUnit(100)), + LogicalSize(LayoutUnit(100), LayoutUnit(100)), /* shrink_to_fit */ true, /* is_new_formatting_context */ true); NGConstraintSpace space200 = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(200), LayoutUnit(100)), + LogicalSize(LayoutUnit(200), LayoutUnit(100)), /* shrink_to_fit */ true, /* is_new_formatting_context */ true); NGConstraintSpace space250 = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(250), LayoutUnit(100)), + LogicalSize(LayoutUnit(250), LayoutUnit(100)), /* shrink_to_fit */ true, /* is_new_formatting_context */ true); NGConstraintSpace space300 = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(300), LayoutUnit(100)), + LogicalSize(LayoutUnit(300), LayoutUnit(100)), /* shrink_to_fit */ true, /* is_new_formatting_context */ true); NGConstraintSpace space400 = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(400), LayoutUnit(100)), + LogicalSize(LayoutUnit(400), LayoutUnit(100)), /* shrink_to_fit */ true, /* is_new_formatting_context */ true); scoped_refptr<const NGLayoutResult> result; @@ -348,41 +350,41 @@ TEST_F(NGBlockLayoutAlgorithmTest, ShrinkToFitCaching) { auto* box4 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box4")); // Ensure we cached the result for box1 in the first layout pass. - result = box1->CachedLayoutResult(space300, nullptr); + result = RunCachedLayoutResult(space300, NGBlockNode(box1)); EXPECT_NE(result.get(), nullptr); // box1 was sized to its max-content size in the first layout pass, passing // an available size larger than the fragment should hit the cache. - result = box1->CachedLayoutResult(space400, nullptr); + result = RunCachedLayoutResult(space400, NGBlockNode(box1)); EXPECT_NE(result.get(), nullptr); // Passing an available size smaller than the fragment should miss the cache // as the fragment may shrink. - result = box1->CachedLayoutResult(space100, nullptr); + result = RunCachedLayoutResult(space100, NGBlockNode(box1)); EXPECT_EQ(result.get(), nullptr); // Ensure we cached the result for box2 in the first layout pass. - result = box2->CachedLayoutResult(space300, nullptr); + result = RunCachedLayoutResult(space300, NGBlockNode(box2)); EXPECT_NE(result.get(), nullptr); // box2 was sized to its min-content size in the first layout pass, passing // an available size smaller than the fragment should hit the cache. - result = box2->CachedLayoutResult(space200, nullptr); + result = RunCachedLayoutResult(space200, NGBlockNode(box2)); EXPECT_NE(result.get(), nullptr); // Passing an available size larger than the fragment should miss the cache // as the fragment may shrink. - result = box2->CachedLayoutResult(space400, nullptr); + result = RunCachedLayoutResult(space400, NGBlockNode(box2)); EXPECT_EQ(result.get(), nullptr); // box3 was sized to its min-content size in the first layout pass, however // it should miss the cache as it has a %-min-size. - result = box3->CachedLayoutResult(space200, nullptr); + result = RunCachedLayoutResult(space200, NGBlockNode(box3)); EXPECT_EQ(result.get(), nullptr); // box4 was sized to its max-content size in the first layout pass (the same // as box1) however it should miss the cache due to its margin. - result = box4->CachedLayoutResult(space250, nullptr); + result = RunCachedLayoutResult(space250, NGBlockNode(box4)); EXPECT_EQ(result.get(), nullptr); } @@ -411,14 +413,14 @@ TEST_F(NGBlockLayoutAlgorithmTest, LineOffsetCaching) { }; NGConstraintSpace space200 = - create_space(NGLogicalSize(LayoutUnit(300), LayoutUnit(100)), + create_space(LogicalSize(LayoutUnit(300), LayoutUnit(100)), NGBfcOffset(LayoutUnit(50), LayoutUnit())); scoped_refptr<const NGLayoutResult> result; auto* box1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box1")); // Ensure we get a cached layout result, even if our BFC line-offset changed. - result = box1->CachedLayoutResult(space200, nullptr); + result = RunCachedLayoutResult(space200, NGBlockNode(box1)); EXPECT_NE(result.get(), nullptr); } @@ -440,21 +442,22 @@ TEST_F(NGBlockLayoutAlgorithmTest, LayoutBlockChildren) { NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), NGSizeIndefinite)); + LogicalSize(LayoutUnit(100), kIndefiniteSize)); - scoped_refptr<const NGPhysicalBoxFragment> frag = - RunBlockLayoutAlgorithm(space, container); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(container, space); - EXPECT_EQ(LayoutUnit(kWidth), frag->Size().width); - EXPECT_EQ(LayoutUnit(kHeight1 + kHeight2 + kMarginTop), frag->Size().height); - EXPECT_EQ(NGPhysicalFragment::kFragmentBox, frag->Type()); - ASSERT_EQ(frag->Children().size(), 2UL); + EXPECT_EQ(LayoutUnit(kWidth), fragment->Size().width); + EXPECT_EQ(LayoutUnit(kHeight1 + kHeight2 + kMarginTop), + fragment->Size().height); + EXPECT_EQ(NGPhysicalFragment::kFragmentBox, fragment->Type()); + ASSERT_EQ(fragment->Children().size(), 2UL); - const NGLink& first_child = frag->Children()[0]; + const NGLink& first_child = fragment->Children()[0]; EXPECT_EQ(kHeight1, first_child->Size().height); EXPECT_EQ(0, first_child.Offset().top); - const NGLink& second_child = frag->Children()[1]; + const NGLink& second_child = fragment->Children()[1]; EXPECT_EQ(kHeight2, second_child->Size().height); EXPECT_EQ(kHeight1 + kMarginTop, second_child.Offset().top); } @@ -484,11 +487,11 @@ TEST_F(NGBlockLayoutAlgorithmTest, LayoutBlockChildrenWithWritingMode) { NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(500), LayoutUnit(500))); - scoped_refptr<const NGPhysicalBoxFragment> frag = - RunBlockLayoutAlgorithm(space, container); + LogicalSize(LayoutUnit(500), LayoutUnit(500))); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(container, space); - const NGLink& child = frag->Children()[0]; + const NGLink& child = fragment->Children()[0]; const NGLink& child2 = static_cast<const NGPhysicalBoxFragment*>(child.get())->Children()[0]; @@ -542,7 +545,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase1WithFloats) { std::tie(fragment, space) = RunBlockLayoutAlgorithmForElement( GetDocument().getElementsByTagName("html")->item(0)); ASSERT_EQ(fragment->Children().size(), 1UL); - NGPhysicalOffset body_offset = fragment->Children()[0].Offset(); + PhysicalOffset body_offset = fragment->Children()[0].Offset(); auto* body_fragment = To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); // 20 = max(first child's margin top, containers's margin top) @@ -555,13 +558,13 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase1WithFloats) { auto* container_fragment = To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); - NGPhysicalOffset container_offset = body_fragment->Children()[0].Offset(); + PhysicalOffset container_offset = body_fragment->Children()[0].Offset(); // 0 = collapsed with body's margin EXPECT_THAT(LayoutUnit(0), container_offset.top); ASSERT_EQ(3UL, container_fragment->Children().size()); - NGPhysicalOffset child_offset = container_fragment->Children()[2].Offset(); + PhysicalOffset child_offset = container_fragment->Children()[2].Offset(); // 0 = collapsed with container's margin EXPECT_THAT(LayoutUnit(0), child_offset.top); @@ -626,13 +629,13 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase2WithFloats) { auto* body_fragment = To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); - NGPhysicalOffset body_offset = fragment->Children()[0].Offset(); + PhysicalOffset body_offset = fragment->Children()[0].Offset(); // -7 = empty1's margin(-15) + body's margin(8) EXPECT_THAT(LayoutUnit(-7), body_offset.top); ASSERT_EQ(4UL, body_fragment->Children().size()); FragmentChildIterator iterator(body_fragment); - NGPhysicalOffset offset; + PhysicalOffset offset; iterator.NextChild(&offset); EXPECT_THAT(LayoutUnit(), offset.top); @@ -698,13 +701,13 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase3) { run_test(Length::Auto()); // Margins are collapsed with the result 200 = std::max(20, 200) // The fragment size 258 == body's margin 8 + child's height 50 + 200 - EXPECT_EQ(NGPhysicalSize(LayoutUnit(800), LayoutUnit(258)), fragment->Size()); + EXPECT_EQ(PhysicalSize(800, 258), fragment->Size()); // height == fixed run_test(Length::Fixed(50)); // Margins are not collapsed, so fragment still has margins == 20. // The fragment size 78 == body's margin 8 + child's height 50 + 20 - EXPECT_EQ(NGPhysicalSize(LayoutUnit(800), LayoutUnit(78)), fragment->Size()); + EXPECT_EQ(PhysicalSize(800, 78), fragment->Size()); } // Verifies that 2 adjoining margins are not collapsed if there is padding or @@ -727,9 +730,9 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase4) { </div> )HTML"); - NGPhysicalOffset body_offset; - NGPhysicalOffset container_offset; - NGPhysicalOffset child_offset; + PhysicalOffset body_offset; + PhysicalOffset container_offset; + PhysicalOffset child_offset; scoped_refptr<const NGPhysicalBoxFragment> fragment; auto run_test = [&](const Length& container_padding_top) { Element* container = GetDocument().getElementById("container"); @@ -752,7 +755,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase4) { run_test(Length::Fixed(20)); // 500 = child's height 50 + 2xmargin 400 + paddint-top 20 + // container's margin 30 - EXPECT_EQ(NGPhysicalSize(LayoutUnit(800), LayoutUnit(500)), fragment->Size()); + EXPECT_EQ(PhysicalSize(800, 500), fragment->Size()); // 30 = max(body's margin 8, container margin 30) EXPECT_EQ(LayoutUnit(30), body_offset.top); // 220 = container's padding top 20 + child's margin @@ -762,7 +765,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase4) { run_test(Length::Fixed(0)); // 450 = 2xmax(body's margin 8, container's margin 30, child's margin 200) + // child's height 50 - EXPECT_EQ(NGPhysicalSize(LayoutUnit(800), LayoutUnit(450)), fragment->Size()); + EXPECT_EQ(PhysicalSize(800, 450), fragment->Size()); // 200 = (body's margin 8, container's margin 30, child's margin 200) EXPECT_EQ(LayoutUnit(200), body_offset.top); // 0 = collapsed margins @@ -804,7 +807,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase5) { // body auto* body_fragment = To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); - NGPhysicalOffset body_offset = fragment->Children()[0].Offset(); + PhysicalOffset body_offset = fragment->Children()[0].Offset(); // 10 = std::max(body's margin 8, container's margin top) int body_top_offset = 10; EXPECT_THAT(body_offset.top, LayoutUnit(body_top_offset)); @@ -812,27 +815,25 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase5) { EXPECT_THAT(body_offset.left, LayoutUnit(body_left_offset)); // height = 70. std::max(vertical height's 70, horizontal's height's 60) - ASSERT_EQ(NGPhysicalSize(LayoutUnit(784), LayoutUnit(70)), - body_fragment->Size()); + ASSERT_EQ(PhysicalSize(784, 70), body_fragment->Size()); ASSERT_EQ(1UL, body_fragment->Children().size()); // container auto* container_fragment = To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); - NGPhysicalOffset container_offset = body_fragment->Children()[0].Offset(); + PhysicalOffset container_offset = body_fragment->Children()[0].Offset(); // Container's margins are collapsed with body's fragment. EXPECT_THAT(container_offset.top, LayoutUnit()); EXPECT_THAT(container_offset.left, LayoutUnit()); ASSERT_EQ(2UL, container_fragment->Children().size()); // vertical - NGPhysicalOffset vertical_offset = container_fragment->Children()[0].Offset(); + PhysicalOffset vertical_offset = container_fragment->Children()[0].Offset(); EXPECT_THAT(vertical_offset.top, LayoutUnit()); EXPECT_THAT(vertical_offset.left, LayoutUnit()); // horizontal - NGPhysicalOffset orizontal_offset = - container_fragment->Children()[1].Offset(); + PhysicalOffset orizontal_offset = container_fragment->Children()[1].Offset(); EXPECT_THAT(orizontal_offset.top, LayoutUnit()); // 130 = vertical's width 30 + // std::max(vertical's margin right 90, horizontal's margin-left 100) @@ -859,13 +860,13 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsWithText) { const NGPhysicalBoxFragment* body_fragment = To<NGPhysicalBoxFragment>(html_fragment->Children()[0].get()); - NGPhysicalOffset body_offset = html_fragment->Children()[0].Offset(); + PhysicalOffset body_offset = html_fragment->Children()[0].Offset(); // 20 = std::max(body's margin, p's margin) - EXPECT_THAT(body_offset, NGPhysicalOffset(LayoutUnit(10), LayoutUnit(20))); + EXPECT_THAT(body_offset, PhysicalOffset(10, 20)); - NGPhysicalOffset p_offset = body_fragment->Children()[0].Offset(); + PhysicalOffset p_offset = body_fragment->Children()[0].Offset(); // Collapsed margins with result = 0. - EXPECT_THAT(p_offset, NGPhysicalOffset(LayoutUnit(20), LayoutUnit(0))); + EXPECT_THAT(p_offset, PhysicalOffset(20, 0)); } // Verifies that the margin strut of a child with a different writing mode does @@ -896,18 +897,18 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase6) { NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(500), LayoutUnit(500))); - scoped_refptr<const NGPhysicalBoxFragment> frag = - RunBlockLayoutAlgorithm(space, container); + LogicalSize(LayoutUnit(500), LayoutUnit(500))); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(container, space); - ASSERT_EQ(frag->Children().size(), 2UL); + ASSERT_EQ(fragment->Children().size(), 2UL); - const NGPhysicalFragment* child1 = frag->Children()[0].get(); - NGPhysicalOffset child1_offset = frag->Children()[0].Offset(); + const NGPhysicalFragment* child1 = fragment->Children()[0].get(); + PhysicalOffset child1_offset = fragment->Children()[0].Offset(); EXPECT_EQ(0, child1_offset.top); EXPECT_EQ(kHeight, child1->Size().height); - NGPhysicalOffset child2_offset = frag->Children()[1].Offset(); + PhysicalOffset child2_offset = fragment->Children()[1].Offset(); EXPECT_EQ(kHeight + std::max(kMarginBottom, kMarginTop), child2_offset.top); } @@ -951,21 +952,21 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase7) { FragmentChildIterator iterator(fragment.get()); // body - NGPhysicalOffset offset; + PhysicalOffset offset; const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(200), LayoutUnit(20)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(8), LayoutUnit(20)), offset); + EXPECT_EQ(PhysicalSize(200, 20), child->Size()); + EXPECT_EQ(PhysicalOffset(8, 20), offset); // #zero iterator.SetParent(child); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(200), LayoutUnit(0)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(0)), offset); + EXPECT_EQ(PhysicalSize(200, 0), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 0), offset); // #inflow child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(200), LayoutUnit(20)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(0)), offset); + EXPECT_EQ(PhysicalSize(200, 20), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 0), offset); } // An empty block level element (with margins collapsing through it) has @@ -1198,15 +1199,15 @@ TEST_F(NGBlockLayoutAlgorithmTest, BorderAndPadding) { NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite)); + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); - scoped_refptr<const NGPhysicalBoxFragment> frag = - RunBlockLayoutAlgorithm(space, container); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(container, space); - ASSERT_EQ(frag->Children().size(), 1UL); + ASSERT_EQ(fragment->Children().size(), 1UL); // div1 - const NGPhysicalFragment* child = frag->Children()[0].get(); + const NGPhysicalFragment* child = fragment->Children()[0].get(); EXPECT_EQ(kBorderLeft + kPaddingLeft + kWidth + kPaddingRight + kBorderRight, child->Size().width); EXPECT_EQ(kBorderTop + kPaddingTop + kHeight + kPaddingBottom + kBorderBottom, @@ -1216,7 +1217,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, BorderAndPadding) { ASSERT_EQ(static_cast<const NGPhysicalBoxFragment*>(child)->Children().size(), 1UL); - NGPhysicalOffset div2_offset = + PhysicalOffset div2_offset = static_cast<const NGPhysicalBoxFragment*>(child)->Children()[0].Offset(); EXPECT_EQ(kBorderTop + kPaddingTop, div2_offset.top); EXPECT_EQ(kBorderLeft + kPaddingLeft, div2_offset.left); @@ -1235,15 +1236,15 @@ TEST_F(NGBlockLayoutAlgorithmTest, PercentageResolutionSize) { NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), NGSizeIndefinite)); - scoped_refptr<const NGPhysicalBoxFragment> frag = - RunBlockLayoutAlgorithm(space, container); + LogicalSize(LayoutUnit(100), kIndefiniteSize)); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(container, space); - EXPECT_EQ(LayoutUnit(kWidth + kPaddingLeft), frag->Size().width); - EXPECT_EQ(NGPhysicalFragment::kFragmentBox, frag->Type()); - ASSERT_EQ(frag->Children().size(), 1UL); + EXPECT_EQ(LayoutUnit(kWidth + kPaddingLeft), fragment->Size().width); + EXPECT_EQ(NGPhysicalFragment::kFragmentBox, fragment->Type()); + ASSERT_EQ(fragment->Children().size(), 1UL); - const NGPhysicalFragment* child = frag->Children()[0].get(); + const NGPhysicalFragment* child = fragment->Children()[0].get(); EXPECT_EQ(LayoutUnit(12), child->Size().width); } @@ -1267,16 +1268,16 @@ TEST_F(NGBlockLayoutAlgorithmTest, AutoMargin) { NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), NGSizeIndefinite)); - scoped_refptr<const NGPhysicalBoxFragment> frag = - RunBlockLayoutAlgorithm(space, container); + LogicalSize(LayoutUnit(100), kIndefiniteSize)); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(container, space); - EXPECT_EQ(LayoutUnit(kWidth + kPaddingLeft), frag->Size().width); - EXPECT_EQ(NGPhysicalFragment::kFragmentBox, frag->Type()); - ASSERT_EQ(1UL, frag->Children().size()); + EXPECT_EQ(LayoutUnit(kWidth + kPaddingLeft), fragment->Size().width); + EXPECT_EQ(NGPhysicalFragment::kFragmentBox, fragment->Type()); + ASSERT_EQ(1UL, fragment->Children().size()); - const NGPhysicalFragment* child = frag->Children()[0].get(); - NGPhysicalOffset child_offset = frag->Children()[0].Offset(); + const NGPhysicalFragment* child = fragment->Children()[0].get(); + PhysicalOffset child_offset = fragment->Children()[0].Offset(); EXPECT_EQ(LayoutUnit(kChildWidth), child->Size().width); EXPECT_EQ(LayoutUnit(kPaddingLeft + 10), child_offset.left); EXPECT_EQ(LayoutUnit(0), child_offset.top); @@ -1334,7 +1335,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, PositionFloatInsideEmptyBlocks) { const auto* body_fragment = To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); - NGPhysicalOffset body_offset = fragment->Children()[0].Offset(); + PhysicalOffset body_offset = fragment->Children()[0].Offset(); FragmentChildIterator iterator(body_fragment); // 20 = std::max(empty1's margin, empty2's margin, body's margin) int body_top_offset = 20; @@ -1345,7 +1346,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, PositionFloatInsideEmptyBlocks) { ASSERT_EQ(1UL, container_fragment->Children().size()); iterator.SetParent(container_fragment); - NGPhysicalOffset offset; + PhysicalOffset offset; const auto* empty1_fragment = iterator.NextChild(&offset); // 0, vertical margins got collapsed EXPECT_THAT(offset.top, LayoutUnit()); @@ -1364,17 +1365,19 @@ TEST_F(NGBlockLayoutAlgorithmTest, PositionFloatInsideEmptyBlocks) { offset = To<NGPhysicalLineBoxFragment>(linebox_fragment)->Children()[0].offset; - // inline 10 = left float's margin(10) + // The floats are positioned outside the line-box as the line-box is + // "avoiding" these floats. + // inline -35 = inline-size of left-float (including margins). // block 10 = left float's margin - EXPECT_THAT(offset, NGPhysicalOffset(LayoutUnit(10), LayoutUnit(10))); + EXPECT_THAT(offset, PhysicalOffset(-35, 10)); offset = To<NGPhysicalLineBoxFragment>(linebox_fragment)->Children()[1].offset; - // inline offset 135 = right float's margin(10) + right float offset(125) + // inline offset 90 = right float's margin(10) + right float offset(80) // block offset 15 = right float's margin - LayoutUnit right_float_offset = LayoutUnit(125); - EXPECT_THAT(offset, NGPhysicalOffset(LayoutUnit(10) + right_float_offset, - LayoutUnit(15))); + LayoutUnit right_float_offset = LayoutUnit(80); + EXPECT_THAT(offset, PhysicalOffset(LayoutUnit(10) + right_float_offset, + LayoutUnit(15))); // ** Verify layout tree ** Element* left_float = GetDocument().getElementById("left-float"); @@ -1445,7 +1448,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, PositionFloatFragments) { ASSERT_EQ(1UL, fragment->Children().size()); const auto* body_fragment = To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); - NGPhysicalOffset body_offset = fragment->Children()[0].Offset(); + PhysicalOffset body_offset = fragment->Children()[0].Offset(); EXPECT_THAT(LayoutUnit(8), body_offset.top); FragmentChildIterator iterator(body_fragment); @@ -1458,7 +1461,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, PositionFloatFragments) { EXPECT_EQ(8, left_float->OffsetTop()); iterator.SetParent(container_fragment); - NGPhysicalOffset offset; + PhysicalOffset offset; iterator.NextChild(&offset); EXPECT_THAT(LayoutUnit(), offset.top); @@ -1556,11 +1559,11 @@ TEST_F(NGBlockLayoutAlgorithmTest, PositionFragmentsWithClear) { </div> )HTML"); - NGPhysicalOffset clerance_offset; - NGPhysicalOffset body_offset; - NGPhysicalOffset container_offset; - NGPhysicalOffset block_offset; - NGPhysicalOffset adjoining_clearance_offset; + PhysicalOffset clerance_offset; + PhysicalOffset body_offset; + PhysicalOffset container_offset; + PhysicalOffset block_offset; + PhysicalOffset adjoining_clearance_offset; scoped_refptr<const NGPhysicalBoxFragment> fragment; auto run_with_clearance = [&](EClear clear_value) { Element* el_with_clear = GetDocument().getElementById("clearance"); @@ -1770,11 +1773,11 @@ TEST_F(NGBlockLayoutAlgorithmTest, ShrinkToFit) { NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(100), NGSizeIndefinite), true); - scoped_refptr<const NGPhysicalFragment> frag = - RunBlockLayoutAlgorithm(space, container); + LogicalSize(LayoutUnit(100), kIndefiniteSize), true); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(container, space); - EXPECT_EQ(LayoutUnit(kWidthChild2), frag->Size().width); + EXPECT_EQ(LayoutUnit(kWidthChild2), fragment->Size().width); } // Verifies that we position empty blocks and floats correctly inside of the @@ -1813,17 +1816,15 @@ TEST_F(NGBlockLayoutAlgorithmTest, PositionEmptyBlocksInNewBfc) { To<NGPhysicalBoxFragment>(html_fragment->Children()[0].get()); auto* container_fragment = To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); - NGPhysicalOffset empty_block1_offset = + PhysicalOffset empty_block1_offset = container_fragment->Children()[1].Offset(); // empty-block1's margin == 8 - EXPECT_THAT(empty_block1_offset, - NGPhysicalOffset(LayoutUnit(8), LayoutUnit(8))); + EXPECT_THAT(empty_block1_offset, PhysicalOffset(8, 8)); - NGPhysicalOffset empty_block2_offset = + PhysicalOffset empty_block2_offset = container_fragment->Children()[2].Offset(); // empty-block2's margin == 50 - EXPECT_THAT(empty_block2_offset, - NGPhysicalOffset(LayoutUnit(0), LayoutUnit(50))); + EXPECT_THAT(empty_block2_offset, PhysicalOffset(0, 50)); } // Verifies that we can correctly position blocks with clearance and @@ -1886,11 +1887,9 @@ TEST_F(NGBlockLayoutAlgorithmTest, // Verify #container-clear block auto* container_clear_fragment = To<NGPhysicalBoxFragment>(body_fragment->Children()[3].get()); - NGPhysicalOffset container_clear_offset = - body_fragment->Children()[3].Offset(); + PhysicalOffset container_clear_offset = body_fragment->Children()[3].Offset(); // 60 = block1's height 30 + std::max(block1's margin 20, zero's margin 30) - EXPECT_THAT(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(60)), - container_clear_offset); + EXPECT_THAT(PhysicalOffset(0, 60), container_clear_offset); Element* container_clear = GetDocument().getElementById("container-clear"); // 190 = block1's margin 130 + block1's height 30 + // std::max(block1's margin 20, zero's margin 30) @@ -1898,12 +1897,11 @@ TEST_F(NGBlockLayoutAlgorithmTest, // Verify #clears-right block ASSERT_EQ(2UL, container_clear_fragment->Children().size()); - NGPhysicalOffset clears_right_offset = + PhysicalOffset clears_right_offset = container_clear_fragment->Children()[1].Offset(); // 20 = right-float's block end offset (130 + 80) - // container_clear->offsetTop() 190 - EXPECT_THAT(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(20)), - clears_right_offset); + EXPECT_THAT(PhysicalOffset(0, 20), clears_right_offset); } // Tests that a block won't fragment if it doesn't reach the fragmentation line. @@ -1924,13 +1922,13 @@ TEST_F(NGBlockLayoutAlgorithmTest, NoFragmentation) { NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); // We should only have one 150x200 fragment with no fragmentation. - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(200)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(150, 200), fragment->Size()); ASSERT_TRUE(fragment->BreakToken()->IsFinished()); } @@ -1952,19 +1950,16 @@ TEST_F(NGBlockLayoutAlgorithmTest, SimpleFragmentation) { NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(200)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(150, 200), fragment->Size()); ASSERT_FALSE(fragment->BreakToken()->IsFinished()); - fragment = NGBlockLayoutAlgorithm( - node, space, To<NGBlockBreakToken>(fragment->BreakToken())) - .Layout() - ->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(100)), fragment->Size()); + fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); + EXPECT_EQ(PhysicalSize(150, 100), fragment->Size()); ASSERT_TRUE(fragment->BreakToken()->IsFinished()); } @@ -1998,37 +1993,34 @@ TEST_F(NGBlockLayoutAlgorithmTest, InnerChildrenFragmentation) { NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(200)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(150, 200), fragment->Size()); ASSERT_FALSE(fragment->BreakToken()->IsFinished()); FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); - NGPhysicalOffset offset; + PhysicalOffset offset; const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(180)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(20)), offset); + EXPECT_EQ(PhysicalSize(150, 180), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 20), offset); EXPECT_FALSE(iterator.NextChild()); - fragment = NGBlockLayoutAlgorithm( - node, space, To<NGBlockBreakToken>(fragment->BreakToken())) - .Layout() - ->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(140)), fragment->Size()); + fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); + EXPECT_EQ(PhysicalSize(150, 140), fragment->Size()); ASSERT_TRUE(fragment->BreakToken()->IsFinished()); iterator.SetParent(To<NGPhysicalBoxFragment>(fragment.get())); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(20)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(0)), offset); + EXPECT_EQ(PhysicalSize(150, 20), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 0), offset); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(100)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(40)), offset); + EXPECT_EQ(PhysicalSize(150, 100), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 40), offset); EXPECT_FALSE(iterator.NextChild()); } @@ -2066,37 +2058,34 @@ TEST_F(NGBlockLayoutAlgorithmTest, NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(200)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(150, 200), fragment->Size()); ASSERT_FALSE(fragment->BreakToken()->IsFinished()); FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); - NGPhysicalOffset offset; + PhysicalOffset offset; const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(180)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(20)), offset); + EXPECT_EQ(PhysicalSize(150, 180), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 20), offset); EXPECT_FALSE(iterator.NextChild()); - fragment = NGBlockLayoutAlgorithm( - node, space, To<NGBlockBreakToken>(fragment->BreakToken())) - .Layout() - ->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(140)), fragment->Size()); + fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); + EXPECT_EQ(PhysicalSize(150, 140), fragment->Size()); ASSERT_TRUE(fragment->BreakToken()->IsFinished()); iterator.SetParent(To<NGPhysicalBoxFragment>(fragment.get())); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(20)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(0)), offset); + EXPECT_EQ(PhysicalSize(150, 20), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 0), offset); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(100)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(40)), offset); + EXPECT_EQ(PhysicalSize(150, 100), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 40), offset); EXPECT_FALSE(iterator.NextChild()); } @@ -2132,37 +2121,34 @@ TEST_F(NGBlockLayoutAlgorithmTest, InnerChildrenFragmentationSmallHeight) { NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(70)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(150, 70), fragment->Size()); ASSERT_FALSE(fragment->BreakToken()->IsFinished()); FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); - NGPhysicalOffset offset; + PhysicalOffset offset; const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(180)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(20)), offset); + EXPECT_EQ(PhysicalSize(150, 180), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 20), offset); EXPECT_FALSE(iterator.NextChild()); - fragment = NGBlockLayoutAlgorithm( - node, space, To<NGBlockBreakToken>(fragment->BreakToken())) - .Layout() - ->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(0)), fragment->Size()); + fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); + EXPECT_EQ(PhysicalSize(150, 0), fragment->Size()); ASSERT_TRUE(fragment->BreakToken()->IsFinished()); iterator.SetParent(To<NGPhysicalBoxFragment>(fragment.get())); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(20)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(0)), offset); + EXPECT_EQ(PhysicalSize(150, 20), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 0), offset); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(100)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(40)), offset); + EXPECT_EQ(PhysicalSize(150, 100), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 40), offset); EXPECT_FALSE(iterator.NextChild()); } @@ -2201,49 +2187,47 @@ TEST_F(NGBlockLayoutAlgorithmTest, DISABLED_FloatFragmentationParallelFlows) { To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(50)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(150, 50), fragment->Size()); ASSERT_FALSE(fragment->BreakToken()->IsFinished()); FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); // First fragment of float1. - NGPhysicalOffset offset; + PhysicalOffset offset; const auto* child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(50), LayoutUnit(150)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(0)), offset); + EXPECT_EQ(PhysicalSize(50, 150), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 0), offset); // First fragment of float2. child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(75), LayoutUnit(150)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(65), LayoutUnit(10)), offset); + EXPECT_EQ(PhysicalSize(75, 150), child->Size()); + EXPECT_EQ(PhysicalOffset(65, 10), offset); space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); - fragment = NGBlockLayoutAlgorithm( - node, space, To<NGBlockBreakToken>(fragment->BreakToken())) - .Layout() - ->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(0)), fragment->Size()); + + fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); + EXPECT_EQ(PhysicalSize(150, 0), fragment->Size()); ASSERT_TRUE(fragment->BreakToken()->IsFinished()); iterator.SetParent(To<NGPhysicalBoxFragment>(fragment.get())); // Second fragment of float1. child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(50), LayoutUnit(50)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(0)), offset); + EXPECT_EQ(PhysicalSize(50, 50), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 0), offset); // Second fragment of float2. child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(75), LayoutUnit(100)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(65), LayoutUnit()), offset); + EXPECT_EQ(PhysicalSize(75, 100), child->Size()); + EXPECT_EQ(PhysicalOffset(65, 0), offset); } // Tests that float children don't fragment if they aren't in the same writing @@ -2281,14 +2265,14 @@ TEST_F(NGBlockLayoutAlgorithmTest, FloatFragmentationOrthogonalFlows) { To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, true, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, true, kFragmentainerSpaceAvailable); AdvanceToLayoutPhase(); - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(60)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(150, 60), fragment->Size()); ASSERT_TRUE(!fragment->BreakToken() || fragment->BreakToken()->IsFinished()); const auto* linebox = @@ -2297,8 +2281,10 @@ TEST_F(NGBlockLayoutAlgorithmTest, FloatFragmentationOrthogonalFlows) { To<NGPhysicalLineBoxFragment>(linebox)->Children()[1].fragment; // float2 should only have one fragment. - EXPECT_EQ(NGPhysicalSize(LayoutUnit(60), LayoutUnit(200)), float2->Size()); - ASSERT_TRUE(!float2->BreakToken() || float2->BreakToken()->IsFinished()); + EXPECT_EQ(PhysicalSize(60, 200), float2->Size()); + ASSERT_TRUE(float2->IsBox()); + NGBreakToken* break_token = To<NGPhysicalBoxFragment>(float2)->BreakToken(); + EXPECT_TRUE(!break_token || break_token->IsFinished()); } // Tests that a float child inside a zero height block fragments correctly. @@ -2331,12 +2317,12 @@ TEST_F(NGBlockLayoutAlgorithmTest, DISABLED_FloatFragmentationZeroHeight) { To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(50)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(150, 50), fragment->Size()); ASSERT_FALSE(fragment->BreakToken()->IsFinished()); FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); @@ -2344,20 +2330,18 @@ TEST_F(NGBlockLayoutAlgorithmTest, DISABLED_FloatFragmentationZeroHeight) { // First fragment of float. iterator.SetParent(child); - NGPhysicalOffset offset; + PhysicalOffset offset; child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(75), LayoutUnit(150)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(10), LayoutUnit(10)), offset); + EXPECT_EQ(PhysicalSize(75, 150), child->Size()); + EXPECT_EQ(PhysicalOffset(10, 10), offset); space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); - fragment = NGBlockLayoutAlgorithm( - node, space, To<NGBlockBreakToken>(fragment->BreakToken())) - .Layout() - ->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(0)), fragment->Size()); + + fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); + EXPECT_EQ(PhysicalSize(150, 0), fragment->Size()); ASSERT_TRUE(fragment->BreakToken()->IsFinished()); iterator.SetParent(To<NGPhysicalBoxFragment>(fragment.get())); @@ -2366,10 +2350,10 @@ TEST_F(NGBlockLayoutAlgorithmTest, DISABLED_FloatFragmentationZeroHeight) { // Second fragment of float. iterator.SetParent(child); child = iterator.NextChild(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(75), LayoutUnit(50)), child->Size()); + EXPECT_EQ(PhysicalSize(75, 50), child->Size()); // TODO(ikilpatrick): Don't include the block-start margin of a float which // has fragmented. - // EXPECT_EQ(NGPhysicalOffset(LayoutUnit(10), LayoutUnit(0)), + // EXPECT_EQ(PhysicalOffset(10, 0), // child->Offset()); } @@ -2397,8 +2381,8 @@ TEST_F(NGBlockLayoutAlgorithmTest, </div> )HTML"); - NGPhysicalOffset body_offset; - NGPhysicalOffset new_fc_offset; + PhysicalOffset body_offset; + PhysicalOffset new_fc_offset; scoped_refptr<const NGPhysicalBoxFragment> fragment; auto run_test = [&](const Length& block_width) { @@ -2420,17 +2404,17 @@ TEST_F(NGBlockLayoutAlgorithmTest, // #new-fc is small enough to fit on the same line with #float. run_test(Length::Fixed(80)); // 100 = float's width, 0 = no margin collapsing - EXPECT_THAT(new_fc_offset, NGPhysicalOffset(LayoutUnit(100), LayoutUnit(0))); + EXPECT_THAT(new_fc_offset, PhysicalOffset(100, 0)); // 8 = body's margins, 20 = new-fc's margin top(20) collapses with // body's margin(8) - EXPECT_THAT(body_offset, NGPhysicalOffset(LayoutUnit(8), LayoutUnit(20))); + EXPECT_THAT(body_offset, PhysicalOffset(8, 20)); // #new-fc is too wide to be positioned on the same line with #float run_test(Length::Fixed(120)); // 30 = #float's height - EXPECT_THAT(new_fc_offset, NGPhysicalOffset(LayoutUnit(0), LayoutUnit(30))); + EXPECT_THAT(new_fc_offset, PhysicalOffset(0, 30)); // 8 = body's margins, no margin collapsing - EXPECT_THAT(body_offset, NGPhysicalOffset(LayoutUnit(8), LayoutUnit(8))); + EXPECT_THAT(body_offset, PhysicalOffset(8, 8)); } TEST_F(NGBlockLayoutAlgorithmTest, NewFcAvoidsFloats) { @@ -2457,22 +2441,22 @@ TEST_F(NGBlockLayoutAlgorithmTest, NewFcAvoidsFloats) { To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite)); + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(200), LayoutUnit(150)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(200, 150), fragment->Size()); FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); - NGPhysicalOffset offset; + PhysicalOffset offset; const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(100), LayoutUnit(30)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(0)), offset); + EXPECT_EQ(PhysicalSize(100, 30), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 0), offset); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(120)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(30)), offset); + EXPECT_EQ(PhysicalSize(150, 120), child->Size()); + EXPECT_EQ(PhysicalOffset(0, 30), offset); } TEST_F(NGBlockLayoutAlgorithmTest, ZeroBlockSizeAboveEdge) { @@ -2493,22 +2477,22 @@ TEST_F(NGBlockLayoutAlgorithmTest, ZeroBlockSizeAboveEdge) { To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, true); + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, true); - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(200), LayoutUnit(10)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(200, 10), fragment->Size()); FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); - NGPhysicalOffset offset; + PhysicalOffset offset; const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(50), LayoutUnit(50)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(-70)), offset); + EXPECT_EQ(PhysicalSize(50, 50), child->Size()); + EXPECT_EQ(PhysicalOffset(0, -70), offset); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(70), LayoutUnit(0)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(-10)), offset); + EXPECT_EQ(PhysicalSize(70, 0), child->Size()); + EXPECT_EQ(PhysicalOffset(0, -10), offset); } TEST_F(NGBlockLayoutAlgorithmTest, NewFcFirstChildIsZeroBlockSize) { @@ -2531,26 +2515,26 @@ TEST_F(NGBlockLayoutAlgorithmTest, NewFcFirstChildIsZeroBlockSize) { To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite), false, true); + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, true); - scoped_refptr<const NGPhysicalFragment> fragment = - NGBlockLayoutAlgorithm(node, space).Layout()->PhysicalFragment(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(200), LayoutUnit(10)), fragment->Size()); + scoped_refptr<const NGPhysicalBoxFragment> fragment = + RunBlockLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(200, 10), fragment->Size()); FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); - NGPhysicalOffset offset; + PhysicalOffset offset; const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(50), LayoutUnit(0)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(-30)), offset); + EXPECT_EQ(PhysicalSize(50, 0), child->Size()); + EXPECT_EQ(PhysicalOffset(0, -30), offset); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(70), LayoutUnit(0)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(-10)), offset); + EXPECT_EQ(PhysicalSize(70, 0), child->Size()); + EXPECT_EQ(PhysicalOffset(0, -10), offset); child = iterator.NextChild(&offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(90), LayoutUnit(20)), child->Size()); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(0), LayoutUnit(-10)), offset); + EXPECT_EQ(PhysicalSize(90, 20), child->Size()); + EXPECT_EQ(PhysicalOffset(0, -10), offset); } // This test assumes that tables are not yet implemented in LayoutNG. @@ -2572,8 +2556,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, RootFragmentOffsetInsideLegacy) { ASSERT_TRUE(fragment); // TODO(crbug.com/781241: Re-enable when we calculate inline offset at // the right time. - // EXPECT_EQ(NGPhysicalOffset(LayoutUnit(20), LayoutUnit(10)), - // fragment->Offset()); + // EXPECT_EQ(PhysicalOffset(20, 10), fragment->Offset()); } // TODO(dgrogan): Move this to ng_flex_layout_algorithm_test.cc if there ever is diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_utils.cc new file mode 100644 index 00000000000..4b3ad59b933 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_utils.cc @@ -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 diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_utils.h new file mode 100644 index 00000000000..a95dbb39bd2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_utils.h @@ -0,0 +1,30 @@ +// 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_BLOCK_LAYOUT_ALGORITHM_UTILS_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_BLOCK_LAYOUT_ALGORITHM_UTILS_H_ + +#include "third_party/blink/renderer/platform/geometry/layout_unit.h" + +namespace blink { + +class ComputedStyle; +struct NGBfcOffset; +class NGExclusionSpace; + +// 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. +// +// This function calculates the inline-offset to avoid floats, and respect +// text-align. +LayoutUnit CalculateOutOfFlowStaticInlineLevelOffset( + const ComputedStyle& container_style, + const NGBfcOffset& origin_bfc_offset, + const NGExclusionSpace&, + LayoutUnit child_available_inline_size); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_BLOCK_LAYOUT_ALGORITHM_UTILS_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.cc index cb72c1725e2..533c2b01271 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.cc @@ -14,7 +14,10 @@ #include "third_party/blink/renderer/core/layout/layout_inline.h" #include "third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.h" #include "third_party/blink/renderer/core/layout/layout_multi_column_set.h" +#include "third_party/blink/renderer/core/layout/layout_table.h" +#include "third_party/blink/renderer/core/layout/layout_table_cell.h" #include "third_party/blink/renderer/core/layout/min_max_size.h" +#include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" @@ -33,6 +36,7 @@ #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_page_layout_algorithm.h" +#include "third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h" #include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h" #include "third_party/blink/renderer/core/paint/paint_layer.h" @@ -56,29 +60,14 @@ inline LayoutMultiColumnFlowThread* GetFlowThread(const LayoutBox& box) { return GetFlowThread(DynamicTo<LayoutBlockFlow>(box)); } -// Parameters to pass when creating a layout algorithm for a block node. -struct NGLayoutAlgorithmParams { - STACK_ALLOCATED(); - - public: - NGLayoutAlgorithmParams(NGBlockNode node, - const NGConstraintSpace& space, - const NGBlockBreakToken* break_token = nullptr) - : node(node), space(space), break_token(break_token) {} - - NGBlockNode node; - const NGConstraintSpace& space; - const NGBlockBreakToken* break_token; -}; - // The entire purpose of this function is to avoid allocating space on the stack // for all layout algorithms for each node we lay out. Therefore it must not be // inline. template <typename Algorithm, typename Callback> NOINLINE void CreateAlgorithmAndRun(const NGLayoutAlgorithmParams& params, const Callback& callback) { - Algorithm algorithm(params.node, params.space, params.break_token); - callback(&algorithm); + std::unique_ptr<Algorithm> algorithm = std::make_unique<Algorithm>(params); + callback(algorithm.get()); } inline void DetermineAlgorithmAndRun( @@ -155,7 +144,9 @@ void UpdateLegacyMultiColumnFlowThread( if (LayoutMultiColumnSet* column_set = flow_thread->FirstMultiColumnSet()) { NGFragment logical_fragment(writing_mode, fragment); auto border_scrollbar_padding = - CalculateBorderScrollbarPadding(constraint_space, node); + ComputeBorders(constraint_space, node) + + ComputeScrollbars(constraint_space, node) + + ComputePadding(constraint_space, node.Style()); column_set->SetLogicalLeft(border_scrollbar_padding.inline_start); column_set->SetLogicalTop(border_scrollbar_padding.block_start); @@ -208,16 +199,12 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( const NGBreakToken* break_token) { // Use the old layout code and synthesize a fragment. if (!CanUseNewLayout()) - return RunOldLayout(constraint_space); + return RunLegacyLayout(constraint_space); - LayoutBlockFlow* block_flow = ToLayoutBlockFlowOrNull(box_); + auto* block_flow = DynamicTo<LayoutBlockFlow>(box_); if (RuntimeEnabledFeatures::TrackLayoutPassesPerBlockEnabled() && block_flow) block_flow->IncrementLayoutPassCount(); - NGLayoutInputNode first_child = FirstChild(); - if (block_flow && !first_child) - block_flow->ClearNGInlineNodeData(); - // The exclusion space internally is a pointer to a shared vector, and // equality of exclusion spaces is performed using pointer comparison on this // internal shared vector. @@ -228,9 +215,13 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( previous_result->GetConstraintSpaceForCaching().ExclusionSpace()); } - scoped_refptr<const NGLayoutResult> layout_result = - box_->CachedLayoutResult(constraint_space, break_token); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + scoped_refptr<const NGLayoutResult> layout_result = box_->CachedLayoutResult( + constraint_space, break_token, &fragment_geometry, &cache_status); if (layout_result) { + DCHECK_EQ(cache_status, NGLayoutCacheStatus::kHit); + // We may have to update the margins on box_; we reuse the layout result // even if a percentage margin may have changed. if (UNLIKELY(Style().MayHaveMargin() && !IsTableCell())) @@ -244,16 +235,44 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( return layout_result; } - PrepareForLayout(); + if (!fragment_geometry) { + fragment_geometry = + CalculateInitialFragmentGeometry(constraint_space, *this); + } - NGBoxStrut old_scrollbars = GetScrollbarSizes(); + PrepareForLayout(); - NGLayoutAlgorithmParams params(*this, constraint_space, + NGLayoutAlgorithmParams params(*this, *fragment_geometry, constraint_space, To<NGBlockBreakToken>(break_token)); - layout_result = LayoutWithAlgorithm(params); + + // Try to perform "simplified" layout. + if (cache_status == NGLayoutCacheStatus::kNeedsSimplifiedLayout && + block_flow && !GetFlowThread(block_flow)) { + // A child may have changed size while performing "simplified" layout (it + // may have gained or removed scrollbars, changing its size). In these + // cases "simplified" layout will return a null layout-result, indicating + // we need to perform a full layout. + layout_result = RunSimplifiedLayout(params); + +#if DCHECK_IS_ON() + if (layout_result) { + layout_result->CheckSameForSimplifiedLayout( + *box_->GetCachedLayoutResult(), /* check_same_block_size */ false); + } +#endif + } + + // Fragment geometry scrollbars are potentially size constrained, and cannot + // be used for comparison with their after layout size. + NGBoxStrut before_layout_scrollbars = + ComputeScrollbars(constraint_space, *this); + + if (!layout_result) + layout_result = LayoutWithAlgorithm(params); FinishLayout(block_flow, constraint_space, break_token, layout_result); - if (old_scrollbars != GetScrollbarSizes()) { + + if (before_layout_scrollbars != ComputeScrollbars(constraint_space, *this)) { // If our scrollbars have changed, we need to relayout because either: // - Our size has changed (if shrinking to fit), or // - Space available to our children has changed. @@ -265,11 +284,16 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( // without that check. PaintLayerScrollableArea::FreezeScrollbarsScope freeze_scrollbars; + // Must not call SetNeedsLayout in intermediate layout. If we do, + // the NeedsLayout flag might not be cleared. crbug.com/967361 + DCHECK(!constraint_space.IsIntermediateLayout() || box_->NeedsLayout()); // Scrollbar changes are hard to detect. Make sure everyone gets the // message. box_->SetNeedsLayout(layout_invalidation_reason::kScrollbarChanged, kMarkOnlyThis); + fragment_geometry = + CalculateInitialFragmentGeometry(constraint_space, *this); layout_result = LayoutWithAlgorithm(params); FinishLayout(block_flow, constraint_space, break_token, layout_result); } @@ -290,9 +314,39 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( return layout_result; } +scoped_refptr<const NGLayoutResult> NGBlockNode::SimplifiedLayout() { + scoped_refptr<const NGLayoutResult> previous_result = + box_->GetCachedLayoutResult(); + DCHECK(previous_result); + + if (!box_->NeedsLayout()) + return previous_result; + + DCHECK(box_->NeedsSimplifiedLayoutOnly()); + + // Perform layout on ourselves using the previous constraint space. + const NGConstraintSpace space( + previous_result->GetConstraintSpaceForCaching()); + scoped_refptr<const NGLayoutResult> result = + Layout(space, /* break_token */ nullptr); + + // If we changed size from performing "simplified" layout, we have + // added/removed scrollbars. Return null indicating to our parent that it + // needs to perform a full layout. + if (previous_result->PhysicalFragment().Size() != + result->PhysicalFragment().Size()) + return nullptr; + +#if DCHECK_IS_ON() + result->CheckSameForSimplifiedLayout(*previous_result); +#endif + + return result; +} + scoped_refptr<const NGLayoutResult> NGBlockNode::CachedLayoutResultForOutOfFlowPositioned( - NGLogicalSize container_content_size) const { + LogicalSize container_content_size) const { DCHECK(IsOutOfFlowPositioned()); if (box_->NeedsLayout()) @@ -349,25 +403,46 @@ void NGBlockNode::FinishLayout( if (!IsBlockLayoutComplete(constraint_space, *layout_result)) return; - DCHECK(layout_result->PhysicalFragment()); - box_->SetCachedLayoutResult(*layout_result, break_token); if (block_flow) { NGLayoutInputNode first_child = FirstChild(); bool has_inline_children = first_child && first_child.IsInline(); - if (has_inline_children || box_->IsLayoutNGFieldset()) { - if (has_inline_children) { - CopyFragmentDataToLayoutBoxForInlineChildren( - To<NGPhysicalBoxFragment>(*layout_result->PhysicalFragment()), - layout_result->PhysicalFragment()->Size().width, - Style().IsFlippedBlocksWritingMode()); - } + // Don't consider display-locked objects as having any children. + if (has_inline_children && + box_->LayoutBlockedByDisplayLock(DisplayLockContext::kChildren)) { + has_inline_children = false; + // It could be the case that our children are already clean at the time + // the lock was acquired. This means that |box_| self dirty bits might be + // set, and child dirty bits might not be. We clear the self bits since we + // want to treat the |box_| as layout clean, even when locked. However, + // here we also skip appending paint fragments for inline children. This + // means that we potentially can end up in a situation where |box_| is + // completely layout clean, but its inline children didn't append the + // paint fragments to it, which causes problems. In order to solve this, + // we set a child dirty bit on |box_| ensuring that when the lock + // is removed, or update is forced, we will visit this box again and + // properly create the paint fragments. See https://crbug.com/962614. + box_->SetChildNeedsLayout(kMarkOnlyThis); + } + + if (has_inline_children) { + const auto& physical_fragment = + To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment()); + CopyFragmentDataToLayoutBoxForInlineChildren( + physical_fragment, physical_fragment.Size().width, + Style().IsFlippedBlocksWritingMode()); block_flow->SetPaintFragment(To<NGBlockBreakToken>(break_token), - layout_result->PhysicalFragment()); + &physical_fragment); + } else if (UNLIKELY(box_->IsLayoutNGFieldset())) { + // TODO(kojii): NGFieldset should not create PaintFragment. + block_flow->ClearNGInlineNodeData(); + block_flow->SetPaintFragment(To<NGBlockBreakToken>(break_token), + &layout_result->PhysicalFragment()); } else { // We still need to clear paint fragments in case it had inline children, // and thus had NGPaintFragment. + block_flow->ClearNGInlineNodeData(); block_flow->SetPaintFragment(To<NGBlockBreakToken>(break_token), nullptr); } } @@ -413,7 +488,7 @@ MinMaxSize NGBlockNode::ComputeMinMaxSize( NGBoxFragment fragment( container_writing_mode, TextDirection::kLtr, // irrelevant here - To<NGPhysicalBoxFragment>(*layout_result->PhysicalFragment())); + To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment())); sizes.min_size = sizes.max_size = fragment.Size().inline_size; if (input.size_type == NGMinMaxSizeType::kContentBoxSize) { sizes -= fragment.Borders().InlineSum() + fragment.Padding().InlineSum() + @@ -424,8 +499,11 @@ MinMaxSize NGBlockNode::ComputeMinMaxSize( return sizes; } + NGFragmentGeometry fragment_geometry = + CalculateInitialMinMaxFragmentGeometry(*constraint_space, *this); base::Optional<MinMaxSize> maybe_sizes = ComputeMinMaxSizeWithAlgorithm( - NGLayoutAlgorithmParams(*this, *constraint_space), input); + NGLayoutAlgorithmParams(*this, fragment_geometry, *constraint_space), + input); if (maybe_sizes.has_value()) { if (UNLIKELY(IsHTMLMarqueeElement(box_->GetNode()) && @@ -443,11 +521,9 @@ MinMaxSize NGBlockNode::ComputeMinMaxSize( // Have to synthesize this value. scoped_refptr<const NGLayoutResult> layout_result = Layout(zero_constraint_space); - NGBoxFragment min_fragment( - container_writing_mode, - TextDirection::kLtr, // irrelevant here - To<NGPhysicalBoxFragment>(*layout_result->PhysicalFragment())); - sizes.min_size = min_fragment.Size().inline_size; + sizes.min_size = + NGFragment(container_writing_mode, layout_result->PhysicalFragment()) + .InlineSize(); // Now, redo with infinite space for max_content NGConstraintSpace infinite_constraint_space = @@ -460,7 +536,7 @@ MinMaxSize NGBlockNode::ComputeMinMaxSize( NGBoxFragment max_fragment( container_writing_mode, TextDirection::kLtr, // irrelevant here - To<NGPhysicalBoxFragment>(*layout_result->PhysicalFragment())); + To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment())); sizes.max_size = max_fragment.Size().inline_size; if (input.size_type == NGMinMaxSizeType::kContentBoxSize) { @@ -498,21 +574,6 @@ MinMaxSize NGBlockNode::ComputeMinMaxSizeFromLegacy( return sizes; } -NGBoxStrut NGBlockNode::GetScrollbarSizes() const { - NGPhysicalBoxStrut sizes; - const ComputedStyle& style = box_->StyleRef(); - if (!style.IsOverflowVisible()) { - LayoutUnit vertical = LayoutUnit(box_->VerticalScrollbarWidth()); - LayoutUnit horizontal = LayoutUnit(box_->HorizontalScrollbarHeight()); - sizes.bottom = horizontal; - if (box_->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) - sizes.left = vertical; - else - sizes.right = vertical; - } - return sizes.ConvertToLogical(style.GetWritingMode(), style.Direction()); -} - NGLayoutInputNode NGBlockNode::NextSibling() const { LayoutObject* next_sibling = GetLayoutObjectForNextSiblingNode(box_); if (next_sibling) { @@ -566,16 +627,15 @@ String NGBlockNode::ToString() const { void NGBlockNode::CopyFragmentDataToLayoutBox( const NGConstraintSpace& constraint_space, const NGLayoutResult& layout_result) { - DCHECK(layout_result.PhysicalFragment()); if (UNLIKELY(constraint_space.IsIntermediateLayout())) return; const auto& physical_fragment = - To<NGPhysicalBoxFragment>(*layout_result.PhysicalFragment()); + To<NGPhysicalBoxFragment>(layout_result.PhysicalFragment()); NGBoxFragment fragment(constraint_space.GetWritingMode(), constraint_space.Direction(), physical_fragment); - NGLogicalSize fragment_logical_size = fragment.Size(); + LogicalSize fragment_logical_size = fragment.Size(); // For each fragment we process, we'll accumulate the logical height and // logical intrinsic content box height. We reset it at the first fragment, // and accumulate at each method call for fragments belonging to the same @@ -601,7 +661,7 @@ void NGBlockNode::CopyFragmentDataToLayoutBox( intrinsic_content_logical_height += layout_result.IntrinsicBlockSize(); NGBoxStrut borders = fragment.Borders(); - NGBoxStrut scrollbars = GetScrollbarSizes(); + NGBoxStrut scrollbars = ComputeScrollbars(constraint_space, *this); NGBoxStrut padding = fragment.Padding(); NGBoxStrut border_scrollbar_padding = borders + scrollbars + padding; @@ -619,12 +679,12 @@ void NGBlockNode::CopyFragmentDataToLayoutBox( box_->SetMargin(ComputePhysicalMargins(constraint_space, Style())); } - LayoutBlockFlow* block_flow = ToLayoutBlockFlowOrNull(box_); + auto* block_flow = DynamicTo<LayoutBlockFlow>(box_); LayoutMultiColumnFlowThread* flow_thread = GetFlowThread(block_flow); if (UNLIKELY(flow_thread)) { PlaceChildrenInFlowThread(constraint_space, physical_fragment); } else { - NGPhysicalOffset offset_from_start; + PhysicalOffset offset_from_start; if (UNLIKELY(constraint_space.HasBlockFragmentation())) { // Need to include any block space that this container has used in // previous fragmentainers. The offset of children will be relative to @@ -665,7 +725,10 @@ void NGBlockNode::CopyFragmentDataToLayoutBox( } box_->UpdateAfterLayout(); - box_->ClearNeedsLayout(); + // We should only clear the child layout bits if display-locking has not + // prevented us from laying the children out. + box_->ClearNeedsLayout( + !box_->LayoutBlockedByDisplayLock(DisplayLockContext::kChildren)); // Overflow computation depends on this being set. if (LIKELY(block_flow)) @@ -675,7 +738,7 @@ void NGBlockNode::CopyFragmentDataToLayoutBox( void NGBlockNode::PlaceChildrenInLayoutBox( const NGConstraintSpace& constraint_space, const NGPhysicalBoxFragment& physical_fragment, - const NGPhysicalOffset& offset_from_start) { + const PhysicalOffset& offset_from_start) { LayoutBox* rendered_legend = nullptr; for (const auto& child_fragment : physical_fragment.Children()) { // Skip any line-boxes we have as children, this is handled within @@ -686,7 +749,7 @@ void NGBlockNode::PlaceChildrenInLayoutBox( const auto& box_fragment = *To<NGPhysicalBoxFragment>(child_fragment.get()); if (IsFirstFragment(constraint_space, box_fragment)) { if (box_fragment.IsRenderedLegend()) - rendered_legend = ToLayoutBox(box_fragment.GetLayoutObject()); + rendered_legend = ToLayoutBox(box_fragment.GetMutableLayoutObject()); CopyChildFragmentPosition(box_fragment, child_fragment.Offset(), offset_from_start); } @@ -718,7 +781,7 @@ void NGBlockNode::PlaceChildrenInFlowThread( DCHECK(child->GetLayoutObject() == box_); // TODO(mstensho): writing modes - NGPhysicalOffset offset(LayoutUnit(), flowthread_offset); + PhysicalOffset offset(LayoutUnit(), flowthread_offset); // Position each child node in the first column that they occur, relatively // to the block-start of the flow thread. @@ -732,9 +795,9 @@ void NGBlockNode::PlaceChildrenInFlowThread( // Copies data back to the legacy layout tree for a given child fragment. void NGBlockNode::CopyChildFragmentPosition( const NGPhysicalFragment& fragment, - const NGPhysicalOffset fragment_offset, - const NGPhysicalOffset additional_offset) { - LayoutBox* layout_box = ToLayoutBox(fragment.GetLayoutObject()); + const PhysicalOffset fragment_offset, + const PhysicalOffset additional_offset) { + LayoutBox* layout_box = ToLayoutBox(fragment.GetMutableLayoutObject()); if (!layout_box) return; @@ -772,17 +835,17 @@ void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren( const NGPhysicalContainerFragment& container, LayoutUnit initial_container_width, bool initial_container_is_flipped, - NGPhysicalOffset offset) { + PhysicalOffset offset) { for (const auto& child : container.Children()) { if (child->IsContainer()) { - NGPhysicalOffset child_offset = offset + child.Offset(); + PhysicalOffset child_offset = offset + child.Offset(); // Replaced elements and inline blocks need Location() set relative to // their block container. - LayoutObject* layout_object = child->GetLayoutObject(); + LayoutObject* layout_object = child->GetMutableLayoutObject(); if (layout_object && layout_object->IsBox()) { LayoutBox& layout_box = ToLayoutBox(*layout_object); - NGPhysicalOffset maybe_flipped_offset = child_offset; + PhysicalOffset maybe_flipped_offset = child_offset; if (initial_container_is_flipped) { maybe_flipped_offset.left = initial_container_width - child->Size().width - @@ -814,7 +877,7 @@ void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren( } bool NGBlockNode::ChildrenInline() const { - if (const auto* block = ToLayoutBlockFlowOrNull(box_)) + if (const auto* block = DynamicTo<LayoutBlockFlow>(box_)) return AreNGBlockFlowChildrenInline(block); return false; } @@ -835,6 +898,13 @@ bool NGBlockNode::UseLogicalBottomMarginEdgeForInlineBlockBaseline() const { layout_box->UseLogicalBottomMarginEdgeForInlineBlockBaseline(); } +bool NGBlockNode::IsRestrictedBlockSizeTableCell() const { + DCHECK(IsTableCell()); + const LayoutTableCell* cell = ToLayoutTableCell(GetLayoutBox()); + return !cell->StyleRef().LogicalHeight().IsAuto() || + !cell->Table()->StyleRef().LogicalHeight().IsAuto(); +} + scoped_refptr<const NGLayoutResult> NGBlockNode::LayoutAtomicInline( const NGConstraintSpace& parent_constraint_space, const ComputedStyle& parent_style, @@ -871,7 +941,7 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::LayoutAtomicInline( return result; } -scoped_refptr<const NGLayoutResult> NGBlockNode::RunOldLayout( +scoped_refptr<const NGLayoutResult> NGBlockNode::RunLegacyLayout( const NGConstraintSpace& constraint_space) { // This is an exit-point from LayoutNG to the legacy engine. This means that // we need to be at a formatting context boundary, since NG and legacy don't @@ -880,12 +950,16 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::RunOldLayout( To<LayoutBlock>(box_)->CreatesNewFormattingContext()); scoped_refptr<const NGLayoutResult> layout_result = - box_->GetCachedLayoutResult(); + box_->IsOutOfFlowPositioned() + ? CachedLayoutResultForOutOfFlowPositioned( + constraint_space.PercentageResolutionSize()) + : box_->GetCachedLayoutResult(); // We need to force a layout on the child if the constraint space given will // change the layout. bool needs_force_relayout = - layout_result && !MaySkipLayout(*this, *layout_result, constraint_space); + layout_result && + !MaySkipLegacyLayout(*this, *layout_result, constraint_space); if (box_->NeedsLayout() || !layout_result || needs_force_relayout) { BoxLayoutExtraInput input(*box_); @@ -901,8 +975,24 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::RunOldLayout( // The sizes should be in the containing block writing mode. std::swap(input.containing_block_content_block_size, input.containing_block_content_inline_size); + + // We cannot lay out without a definite containing block inline-size. We + // end up here if we're performing a measure pass (as part of resolving + // the intrinsic min/max inline-size of some ancestor, for instance). + // Legacy layout has a tendency of clamping negative sizes to 0 anyway, + // but this is missing when it comes to resolving percentage-based + // padding, for instance. + if (input.containing_block_content_inline_size == kIndefiniteSize) { + DCHECK(constraint_space.IsIntermediateLayout()); + input.containing_block_content_inline_size = LayoutUnit(); + } } } + + // We need a definite containing block inline-size, or we'd be unable to + // resolve percentages. + DCHECK_GE(input.containing_block_content_inline_size, LayoutUnit()); + input.available_inline_size = constraint_space.AvailableSize().inline_size; if (constraint_space.IsFixedSizeInline()) @@ -920,26 +1010,43 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::RunOldLayout( box_->ForceLayout(); // Synthesize a new layout result. - NGLogicalSize box_size(box_->LogicalWidth(), box_->LogicalHeight()); + NGFragmentGeometry fragment_geometry; + fragment_geometry.border_box_size = {box_->LogicalWidth(), + box_->LogicalHeight()}; + fragment_geometry.border = {box_->BorderStart(), box_->BorderEnd(), + box_->BorderBefore(), box_->BorderAfter()}; + fragment_geometry.scrollbar = ComputeScrollbars(constraint_space, *this); + fragment_geometry.padding = {box_->PaddingStart(), box_->PaddingEnd(), + box_->PaddingBefore(), box_->PaddingAfter()}; + // TODO(kojii): Implement use_first_line_style. NGBoxFragmentBuilder builder(*this, box_->Style(), &constraint_space, writing_mode, box_->StyleRef().Direction()); builder.SetIsNewFormattingContext( constraint_space.IsNewFormattingContext()); - builder.SetIsOldLayoutRoot(); - builder.SetInlineSize(box_size.inline_size); - builder.SetBlockSize(box_size.block_size); - NGBoxStrut borders(box_->BorderStart(), box_->BorderEnd(), - box_->BorderBefore(), box_->BorderAfter()); - builder.SetBorders(borders); - NGBoxStrut padding(box_->PaddingStart(), box_->PaddingEnd(), - box_->PaddingBefore(), box_->PaddingAfter()); - builder.SetPadding(padding); - - CopyBaselinesFromOldLayout(constraint_space, &builder); + builder.SetInitialFragmentGeometry(fragment_geometry); + builder.SetIsLegacyLayoutRoot(); + + CopyBaselinesFromLegacyLayout(constraint_space, &builder); layout_result = builder.ToBoxFragment(); box_->SetCachedLayoutResult(*layout_result, /* break_token */ nullptr); + } else if (layout_result) { + // OOF-positioned nodes have a two-tier cache, and their layout results + // must always contain the correct percentage resolution size. + // See |NGBlockNode::CachedLayoutResultForOutOfFlowPositioned|. + const NGConstraintSpace& old_space = + layout_result->GetConstraintSpaceForCaching(); + bool needs_cached_result_update = + IsOutOfFlowPositioned() && + constraint_space.PercentageResolutionSize() != + old_space.PercentageResolutionSize(); + if (needs_cached_result_update) { + layout_result = base::AdoptRef(new NGLayoutResult( + *layout_result, constraint_space, layout_result->BfcLineOffset(), + layout_result->BfcBlockOffset())); + box_->SetCachedLayoutResult(*layout_result, /* break_token */ nullptr); + } } UpdateShapeOutsideInfoIfNeeded( @@ -948,7 +1055,13 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::RunOldLayout( return layout_result; } -void NGBlockNode::CopyBaselinesFromOldLayout( +scoped_refptr<const NGLayoutResult> NGBlockNode::RunSimplifiedLayout( + const NGLayoutAlgorithmParams& params) const { + return NGSimplifiedLayoutAlgorithm(params, *box_->GetCachedLayoutResult()) + .Layout(); +} + +void NGBlockNode::CopyBaselinesFromLegacyLayout( const NGConstraintSpace& constraint_space, NGBoxFragmentBuilder* builder) { const NGBaselineRequestList requests = constraint_space.BaselineRequests(); @@ -962,7 +1075,7 @@ void NGBlockNode::CopyBaselinesFromOldLayout( switch (request.AlgorithmType()) { case NGBaselineAlgorithmType::kAtomicInline: { LayoutUnit position = - AtomicInlineBaselineFromOldLayout(request, constraint_space); + AtomicInlineBaselineFromLegacyLayout(request, constraint_space); if (position != -1) builder->AddBaseline(request, position); break; @@ -977,7 +1090,7 @@ void NGBlockNode::CopyBaselinesFromOldLayout( } } -LayoutUnit NGBlockNode::AtomicInlineBaselineFromOldLayout( +LayoutUnit NGBlockNode::AtomicInlineBaselineFromLegacyLayout( const NGBaselineRequest& request, const NGConstraintSpace& constraint_space) { LineDirectionMode line_direction = box_->IsHorizontalWritingMode() @@ -1015,8 +1128,7 @@ void NGBlockNode::UpdateShapeOutsideInfoIfNeeded( // The box_ may not have a valid size yet (due to an intermediate layout), // use the fragment's size instead. - DCHECK(layout_result.PhysicalFragment()); - LayoutSize box_size = layout_result.PhysicalFragment()->Size().ToLayoutSize(); + LayoutSize box_size = layout_result.PhysicalFragment().Size().ToLayoutSize(); // TODO(ikilpatrick): Ideally this should be moved to a NGLayoutResult // computing the shape area. There may be an issue with the new fragmentation @@ -1030,14 +1142,14 @@ void NGBlockNode::UpdateShapeOutsideInfoIfNeeded( percentage_resolution_inline_size); } -void NGBlockNode::UseOldOutOfFlowPositioning() const { +void NGBlockNode::UseLegacyOutOfFlowPositioning() const { DCHECK(box_->IsOutOfFlowPositioned()); box_->ContainingBlock()->InsertPositionedObject(box_); } // Save static position for legacy AbsPos layout. void NGBlockNode::SaveStaticOffsetForLegacy( - const NGLogicalOffset& offset, + const LogicalOffset& offset, const LayoutObject* offset_container) { DCHECK(box_->IsOutOfFlowPositioned()); // Only set static position if the current offset container diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.h index 43a54e5fb0b..89b39fa7356 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.h @@ -6,7 +6,7 @@ #define NGBlockNode_h #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_offset.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h" #include "third_party/blink/renderer/platform/fonts/font_baseline.h" #include "third_party/blink/renderer/platform/wtf/casting.h" @@ -24,7 +24,8 @@ class NGPhysicalContainerFragment; class NGPhysicalFragment; struct MinMaxSize; struct NGBoxStrut; -struct NGLogicalOffset; +struct NGLayoutAlgorithmParams; +struct LogicalOffset; // Represents a node to be laid out. class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { @@ -38,6 +39,13 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { const NGConstraintSpace& constraint_space, const NGBreakToken* break_token = nullptr); + // This method is just for use within the |NGSimplifiedLayoutAlgorithm|. + // + // If layout is dirty, it will perform layout using the previous constraint + // space used to generate the |NGLayoutResult|. + // Otherwise it will simply return the previous layout result generated. + scoped_refptr<const NGLayoutResult> SimplifiedLayout(); + // This method is just for use within the |NGOutOfFlowLayoutPart|. // // As OOF-positioned objects have their position, and size computed @@ -47,7 +55,7 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { // If the containing-block size hasn't changed, and we are layout-clean we // can reuse the previous layout result. scoped_refptr<const NGLayoutResult> CachedLayoutResultForOutOfFlowPositioned( - NGLogicalSize container_content_size) const; + LogicalSize container_content_size) const; NGLayoutInputNode NextSibling() const; @@ -75,8 +83,6 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { MinMaxSize ComputeMinMaxSizeFromLegacy(const MinMaxSizeInput&) const; - NGBoxStrut GetScrollbarSizes() const; - NGLayoutInputNode FirstChild() const; NGBlockNode GetRenderedLegend() const; @@ -90,6 +96,10 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { // See comments in UseLogicalBottomMarginEdgeForInlineBlockBaseline(). bool UseLogicalBottomMarginEdgeForInlineBlockBaseline() const; + // Return true if the block size of this table cell should be considered + // restricted (e.g. height of the cell or its table is non-auto). + bool IsRestrictedBlockSizeTableCell() const; + // Layout an atomic inline; e.g., inline block. scoped_refptr<const NGLayoutResult> LayoutAtomicInline( const NGConstraintSpace& parent_constraint_space, @@ -97,16 +107,12 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { FontBaseline, bool use_first_line_style); - // Runs layout on the underlying LayoutObject and creates a fragment for the - // resulting geometry. - scoped_refptr<const NGLayoutResult> RunOldLayout(const NGConstraintSpace&); - // Called if this is an out-of-flow block which needs to be // positioned with legacy layout. - void UseOldOutOfFlowPositioning() const; + void UseLegacyOutOfFlowPositioning() const; // Save static position for legacy AbsPos layout. - void SaveStaticOffsetForLegacy(const NGLogicalOffset&, + void SaveStaticOffsetForLegacy(const LogicalOffset&, const LayoutObject* offset_container); // Write back resolved margins to legacy. @@ -120,6 +126,13 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { private: void PrepareForLayout(); + // Runs layout on the underlying LayoutObject and creates a fragment for the + // resulting geometry. + scoped_refptr<const NGLayoutResult> RunLegacyLayout(const NGConstraintSpace&); + + scoped_refptr<const NGLayoutResult> RunSimplifiedLayout( + const NGLayoutAlgorithmParams&) const; + // If this node is a LayoutNGMixin, the caller must pass the layout object for // this node cast to a LayoutBlockFlow as the first argument. void FinishLayout(LayoutBlockFlow*, @@ -135,21 +148,21 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { const NGPhysicalContainerFragment& container, LayoutUnit initial_container_width, bool initial_container_is_flipped, - NGPhysicalOffset offset = {}); + PhysicalOffset offset = {}); void PlaceChildrenInLayoutBox(const NGConstraintSpace&, const NGPhysicalBoxFragment&, - const NGPhysicalOffset& offset_from_start); + const PhysicalOffset& offset_from_start); void PlaceChildrenInFlowThread(const NGConstraintSpace&, const NGPhysicalBoxFragment&); void CopyChildFragmentPosition( const NGPhysicalFragment& fragment, - const NGPhysicalOffset fragment_offset, - const NGPhysicalOffset additional_offset = NGPhysicalOffset()); + const PhysicalOffset fragment_offset, + const PhysicalOffset additional_offset = PhysicalOffset()); - void CopyBaselinesFromOldLayout(const NGConstraintSpace&, - NGBoxFragmentBuilder*); - LayoutUnit AtomicInlineBaselineFromOldLayout(const NGBaselineRequest&, - const NGConstraintSpace&); + void CopyBaselinesFromLegacyLayout(const NGConstraintSpace&, + NGBoxFragmentBuilder*); + LayoutUnit AtomicInlineBaselineFromLegacyLayout(const NGBaselineRequest&, + const NGConstraintSpace&); void UpdateShapeOutsideInfoIfNeeded( const NGLayoutResult&, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.h index 4c553a3d4a4..53be16265c6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.h @@ -38,16 +38,23 @@ class CORE_EXPORT NGBoxFragment final : public NGFragment { const NGConstraintSpace&) const; NGBoxStrut Borders() const { - const auto& physical_fragment = + const NGPhysicalBoxFragment& physical_box_fragment = To<NGPhysicalBoxFragment>(physical_fragment_); - return physical_fragment.Borders().ConvertToLogical(GetWritingMode(), - direction_); + return physical_box_fragment.Borders().ConvertToLogical(GetWritingMode(), + direction_); } NGBoxStrut Padding() const { - const auto& physical_fragment = + const NGPhysicalBoxFragment& physical_box_fragment = To<NGPhysicalBoxFragment>(physical_fragment_); - return physical_fragment.Padding().ConvertToLogical(GetWritingMode(), - direction_); + return physical_box_fragment.Padding().ConvertToLogical(GetWritingMode(), + direction_); + } + + NGBorderEdges BorderEdges() const { + const NGPhysicalBoxFragment& physical_box_fragment = + To<NGPhysicalBoxFragment>(physical_fragment_); + return NGBorderEdges::FromPhysical(physical_box_fragment.BorderEdges(), + GetWritingMode()); } protected: diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc index 243fdbf5b94..21b54f6c1d1 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc @@ -17,25 +17,30 @@ #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h" +#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" namespace blink { namespace { +// std::pair.first points to the start linebox fragment. +// std::pair.second points to the end linebox fragment. using LineBoxPair = std::pair<const NGPhysicalLineBoxFragment*, const NGPhysicalLineBoxFragment*>; + void GatherInlineContainerFragmentsFromLinebox( NGBoxFragmentBuilder::InlineContainingBlockMap* inline_containing_block_map, HashMap<const LayoutObject*, LineBoxPair>* containing_linebox_map, - const NGPhysicalLineBoxFragment* linebox, - const NGPhysicalOffset linebox_offset) { - for (auto& descendant : NGInlineFragmentTraversal::DescendantsOf(*linebox)) { + const NGPhysicalLineBoxFragment& linebox, + const PhysicalOffset linebox_offset) { + for (const auto& descendant : + NGInlineFragmentTraversal::DescendantsOf(linebox)) { if (!descendant.fragment->IsBox()) continue; - LayoutObject* key = descendant.fragment->GetLayoutObject(); + const LayoutObject* key = descendant.fragment->GetLayoutObject(); // Key for inline is the continuation root if it exists. if (key->IsLayoutInline() && key->GetNode()) - key = key->GetNode()->GetLayoutObject(); + key = key->ContinuationRoot(); auto it = inline_containing_block_map->find(key); if (it == inline_containing_block_map->end()) { // Default case, not one of the blocks we are looking for. @@ -51,22 +56,22 @@ void GatherInlineContainerFragmentsFromLinebox( // |DescendantsOf| returns the offset from the given fragment. Since // we give it the line box, need to add the |linebox_offset|. - NGPhysicalOffsetRect fragment_rect( + PhysicalRect fragment_rect( linebox_offset + descendant.offset_to_container_box, descendant.fragment->Size()); - if (containing_lineboxes.first == linebox) { + if (containing_lineboxes.first == &linebox) { containing_block_geometry->start_fragment_union_rect.Unite(fragment_rect); } else if (!containing_lineboxes.first) { - containing_lineboxes.first = linebox; + containing_lineboxes.first = &linebox; containing_block_geometry = - NGBoxFragmentBuilder::InlineContainingBlockGeometry{ - fragment_rect, NGPhysicalOffsetRect()}; + NGBoxFragmentBuilder::InlineContainingBlockGeometry{fragment_rect, + PhysicalRect()}; } // Skip fragments within an empty line boxes for the end fragment. - if (containing_lineboxes.second == linebox) { + if (containing_lineboxes.second == &linebox) { containing_block_geometry->end_fragment_union_rect.Unite(fragment_rect); - } else if (!containing_lineboxes.second || !linebox->IsEmptyLineBox()) { - containing_lineboxes.second = linebox; + } else if (!containing_lineboxes.second || !linebox.IsEmptyLineBox()) { + containing_lineboxes.second = &linebox; containing_block_geometry->end_fragment_union_rect = fragment_rect; } } @@ -78,7 +83,6 @@ void NGBoxFragmentBuilder::RemoveChildren() { child_break_tokens_.resize(0); inline_break_tokens_.resize(0); children_.resize(0); - offsets_.resize(0); } NGBoxFragmentBuilder& NGBoxFragmentBuilder::AddBreakBeforeChild( @@ -117,13 +121,12 @@ NGBoxFragmentBuilder& NGBoxFragmentBuilder::AddBreakBeforeLine( DCHECK_GT(children_.size(), 0UL); for (int i = children_.size() - 1; i >= 0; i--) { DCHECK_NE(i, 0); - if (!children_[i]->IsLineBox()) + if (!children_[i].fragment->IsLineBox()) continue; if (!--lines_to_remove) { // This is the first line that is going to the next fragment. Remove it, // and everything after it. children_.resize(i); - offsets_.resize(i); break; } } @@ -141,7 +144,7 @@ NGBoxFragmentBuilder& NGBoxFragmentBuilder::PropagateBreak( if (LIKELY(!has_block_fragmentation_)) return *this; if (!did_break_) - PropagateBreak(*child_layout_result.PhysicalFragment()); + PropagateBreak(child_layout_result.PhysicalFragment()); if (child_layout_result.HasForcedBreak()) SetHasForcedBreak(); else @@ -150,7 +153,7 @@ NGBoxFragmentBuilder& NGBoxFragmentBuilder::PropagateBreak( } NGBoxFragmentBuilder& NGBoxFragmentBuilder::PropagateBreak( - const NGPhysicalFragment& child_fragment) { + const NGPhysicalContainerFragment& child_fragment) { DCHECK(has_block_fragmentation_); if (!did_break_) { const auto* token = child_fragment.BreakToken(); @@ -162,35 +165,37 @@ NGBoxFragmentBuilder& NGBoxFragmentBuilder::PropagateBreak( void NGBoxFragmentBuilder::AddOutOfFlowLegacyCandidate( NGBlockNode node, const NGStaticPosition& static_position, - LayoutObject* inline_container) { + const LayoutInline* inline_container) { DCHECK_GE(InlineSize(), LayoutUnit()); DCHECK_GE(BlockSize(), LayoutUnit()); - NGOutOfFlowPositionedDescendant descendant{node, static_position, - inline_container}; + NGOutOfFlowPositionedDescendant descendant( + node, static_position, + inline_container ? ToLayoutInline(inline_container->ContinuationRoot()) + : nullptr); // Need 0,0 physical coordinates as child offset. Because offset // is stored as logical, must convert physical 0,0 to logical. - NGLogicalOffset zero_offset; + LogicalOffset zero_offset; switch (GetWritingMode()) { case WritingMode::kHorizontalTb: if (IsLtr(Direction())) - zero_offset = NGLogicalOffset(); + zero_offset = LogicalOffset(); else - zero_offset = NGLogicalOffset(InlineSize(), LayoutUnit()); + zero_offset = LogicalOffset(InlineSize(), LayoutUnit()); break; case WritingMode::kVerticalRl: case WritingMode::kSidewaysRl: if (IsLtr(Direction())) - zero_offset = NGLogicalOffset(LayoutUnit(), BlockSize()); + zero_offset = LogicalOffset(LayoutUnit(), BlockSize()); else - zero_offset = NGLogicalOffset(InlineSize(), BlockSize()); + zero_offset = LogicalOffset(InlineSize(), BlockSize()); break; case WritingMode::kVerticalLr: case WritingMode::kSidewaysLr: if (IsLtr(Direction())) - zero_offset = NGLogicalOffset(); + zero_offset = LogicalOffset(); else - zero_offset = NGLogicalOffset(InlineSize(), LayoutUnit()); + zero_offset = LogicalOffset(InlineSize(), LayoutUnit()); break; } oof_positioned_candidates_.push_back( @@ -274,27 +279,27 @@ void NGBoxFragmentBuilder::ComputeInlineContainerFragments( // and will break if this changes. DCHECK_GE(InlineSize(), LayoutUnit()); DCHECK_GE(BlockSize(), LayoutUnit()); +#if DCHECK_IS_ON() + // Make sure all entries are continuation root. + for (const auto& entry : *inline_containing_block_map) + DCHECK_EQ(entry.key, entry.key->ContinuationRoot()); +#endif - // std::pair.first points to the start linebox fragment. - // std::pair.second points to the end linebox fragment. - using LineBoxPair = std::pair<const NGPhysicalLineBoxFragment*, - const NGPhysicalLineBoxFragment*>; HashMap<const LayoutObject*, LineBoxPair> containing_linebox_map; - - for (wtf_size_t i = 0; i < children_.size(); i++) { - if (children_[i]->IsLineBox()) { - const auto* linebox = To<NGPhysicalLineBoxFragment>(children_[i].get()); - const NGPhysicalOffset linebox_offset = offsets_[i].ConvertToPhysical( + for (const auto& child : children_) { + if (child.fragment->IsLineBox()) { + const auto& linebox = To<NGPhysicalLineBoxFragment>(*child.fragment); + const PhysicalOffset linebox_offset = child.offset.ConvertToPhysical( GetWritingMode(), Direction(), - ToNGPhysicalSize(Size(), GetWritingMode()), linebox->Size()); + ToPhysicalSize(Size(), GetWritingMode()), linebox.Size()); GatherInlineContainerFragmentsFromLinebox(inline_containing_block_map, &containing_linebox_map, linebox, linebox_offset); - } else if (children_[i]->IsBox()) { - const auto* box_fragment = To<NGPhysicalBoxFragment>(children_[i].get()); + } else if (child.fragment->IsBox()) { + const auto& box_fragment = To<NGPhysicalBoxFragment>(*child.fragment); bool is_anonymous_container = - box_fragment->GetLayoutObject() && - box_fragment->GetLayoutObject()->IsAnonymousBlock(); + box_fragment.GetLayoutObject() && + box_fragment.GetLayoutObject()->IsAnonymousBlock(); if (!is_anonymous_container) continue; // If child is an anonymous container, this might be a special case of @@ -303,15 +308,15 @@ void NGBoxFragmentBuilder::ComputeInlineContainerFragments( // lineboxes inside anonymous box. // For more on this special case, see "css container is an inline, with // inline splitting" comment in NGOutOfFlowLayoutPart::LayoutDescendant. - const NGPhysicalOffset box_offset = offsets_[i].ConvertToPhysical( + const PhysicalOffset box_offset = child.offset.ConvertToPhysical( GetWritingMode(), Direction(), - ToNGPhysicalSize(Size(), GetWritingMode()), box_fragment->Size()); + ToPhysicalSize(Size(), GetWritingMode()), box_fragment.Size()); // Traverse lineboxes of anonymous box. - for (const auto& child : box_fragment->Children()) { - if (child->IsLineBox()) { - const auto* linebox = To<NGPhysicalLineBoxFragment>(child.get()); - const NGPhysicalOffset linebox_offset = child.Offset() + box_offset; + for (const auto& box_child : box_fragment.Children()) { + if (box_child->IsLineBox()) { + const auto& linebox = To<NGPhysicalLineBoxFragment>(*box_child); + const PhysicalOffset linebox_offset = box_child.Offset() + box_offset; GatherInlineContainerFragmentsFromLinebox(inline_containing_block_map, &containing_linebox_map, linebox, linebox_offset); @@ -321,4 +326,17 @@ void NGBoxFragmentBuilder::ComputeInlineContainerFragments( } } +#if DCHECK_IS_ON() + +void NGBoxFragmentBuilder::CheckNoBlockFragmentation() const { + DCHECK(!did_break_); + DCHECK(!has_forced_break_); + DCHECK_EQ(used_block_size_, LayoutUnit()); + DCHECK_EQ(minimal_space_shortage_, LayoutUnit::Max()); + DCHECK_EQ(initial_break_before_, EBreakBetween::kAuto); + DCHECK_EQ(previous_break_after_, EBreakBetween::kAuto); +} + +#endif + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h index 73e6b320266..29ffa6702be 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h @@ -5,9 +5,10 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_BOX_FRAGMENT_BUILDER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_BOX_FRAGMENT_BUILDER_H_ +#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_border_edges.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h" +#include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" #include "third_party/blink/renderer/core/layout/ng/ng_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h" @@ -36,9 +37,7 @@ class CORE_EXPORT NGBoxFragmentBuilder final writing_mode, direction), box_type_(NGPhysicalFragment::NGBoxType::kNormalBox), - did_break_(false) { - layout_object_ = node.GetLayoutBox(); - } + did_break_(false) {} // Build a fragment for LayoutObject without NGLayoutInputNode. LayoutInline // has NGInlineItem but does not have corresponding NGLayoutInputNode. @@ -56,30 +55,35 @@ class CORE_EXPORT NGBoxFragmentBuilder final layout_object_ = layout_object; } + NGBoxFragmentBuilder& SetInitialFragmentGeometry( + const NGFragmentGeometry& initial_fragment_geometry) { + initial_fragment_geometry_ = &initial_fragment_geometry; + size_ = initial_fragment_geometry_->border_box_size; + is_initial_block_size_indefinite_ = size_.block_size == kIndefiniteSize; + return *this; + } + NGBoxFragmentBuilder& SetIntrinsicBlockSize(LayoutUnit intrinsic_block_size) { intrinsic_block_size_ = intrinsic_block_size; return *this; } - NGBoxFragmentBuilder& SetBorders(const NGBoxStrut& border) { + const NGBoxStrut& Borders() const { + DCHECK(initial_fragment_geometry_); DCHECK_NE(BoxType(), NGPhysicalFragment::kInlineBox); - borders_ = border; - return *this; + return initial_fragment_geometry_->border; } - const NGBoxStrut& Borders() const { return borders_; } - NGBoxFragmentBuilder& SetPadding(const NGBoxStrut& padding) { - DCHECK_NE(BoxType(), NGPhysicalFragment::kInlineBox); - padding_ = padding; - return *this; + const NGBoxStrut& Scrollbar() const { + DCHECK(initial_fragment_geometry_); + return initial_fragment_geometry_->scrollbar; } - NGBoxFragmentBuilder& SetPadding(const NGLineBoxStrut& padding) { - DCHECK_EQ(BoxType(), NGPhysicalFragment::kInlineBox); - // Convert to flow-relative, because ToInlineBoxFragment() will convert - // the padding to physical coordinates using flow-relative writing-mode. - padding_ = NGBoxStrut(padding, IsFlippedLinesWritingMode(GetWritingMode())); - return *this; + const NGBoxStrut& Padding() const { + DCHECK(initial_fragment_geometry_); + return initial_fragment_geometry_->padding; + } + const LogicalSize& InitialBorderBoxSize() const { + DCHECK(initial_fragment_geometry_); + return initial_fragment_geometry_->border_box_size; } - const NGBoxStrut& Padding() const { return padding_; } - // Remove all children. void RemoveChildren(); @@ -93,11 +97,11 @@ class CORE_EXPORT NGBoxFragmentBuilder final // Update if we have fragmented in this flow. NGBoxFragmentBuilder& PropagateBreak(const NGLayoutResult&); - NGBoxFragmentBuilder& PropagateBreak(const NGPhysicalFragment&); + NGBoxFragmentBuilder& PropagateBreak(const NGPhysicalContainerFragment&); void AddOutOfFlowLegacyCandidate(NGBlockNode, const NGStaticPosition&, - LayoutObject* inline_container); + const LayoutInline* inline_container); // Set how much of the block size we've used so far for this box. NGBoxFragmentBuilder& SetUsedBlockSize(LayoutUnit used_block_size) { @@ -168,9 +172,6 @@ class CORE_EXPORT NGBoxFragmentBuilder final scoped_refptr<const NGLayoutResult> Abort( NGLayoutResult::NGLayoutResultStatus); - // A vector of child offsets. Initially set by AddChild(). - const OffsetVector& Offsets() const { return offsets_; } - NGPhysicalFragment::NGBoxType BoxType() const; NGBoxFragmentBuilder& SetBoxType(NGPhysicalFragment::NGBoxType box_type) { box_type_ = box_type; @@ -180,8 +181,8 @@ class CORE_EXPORT NGBoxFragmentBuilder final is_fieldset_container_ = true; return *this; } - NGBoxFragmentBuilder& SetIsOldLayoutRoot() { - is_old_layout_root_ = true; + NGBoxFragmentBuilder& SetIsLegacyLayoutRoot() { + is_legacy_layout_root_ = true; return *this; } @@ -213,26 +214,32 @@ class CORE_EXPORT NGBoxFragmentBuilder final struct InlineContainingBlockGeometry { DISALLOW_NEW(); // Union of fragments generated on the first line. - NGPhysicalOffsetRect start_fragment_union_rect; + PhysicalRect start_fragment_union_rect; // Union of fragments generated on the last line. - NGPhysicalOffsetRect end_fragment_union_rect; + PhysicalRect end_fragment_union_rect; }; using InlineContainingBlockMap = HashMap<const LayoutObject*, base::Optional<InlineContainingBlockGeometry>>; void ComputeInlineContainerFragments( - InlineContainingBlockMap* inline_container_fragments); + InlineContainingBlockMap* inline_containing_block_map); + +#if DCHECK_IS_ON() + // If we don't participate in a fragmentation context, this method can check + // that all block fragmentation related fields have their initial value. + void CheckNoBlockFragmentation() const; +#endif private: scoped_refptr<const NGLayoutResult> ToBoxFragment(WritingMode); + const NGFragmentGeometry* initial_fragment_geometry_ = nullptr; LayoutUnit intrinsic_block_size_; - NGBoxStrut borders_; - NGBoxStrut padding_; NGPhysicalFragment::NGBoxType box_type_; bool is_fieldset_container_ = false; + bool is_initial_block_size_indefinite_ = false; bool did_break_; bool has_forced_break_ = false; bool is_new_fc_ = false; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_break_token.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_break_token.cc index 4a2c878514b..3d272e1a1a7 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_break_token.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_break_token.cc @@ -22,7 +22,7 @@ static_assert(sizeof(NGBreakToken) == sizeof(SameSizeAsNGBreakToken), } // namespace -#ifndef NDEBUG +#if DCHECK_IS_ON() namespace { @@ -62,6 +62,6 @@ void NGBreakToken::ShowBreakTokenTree() const { AppendBreakTokenToString(this, &string_builder); fprintf(stderr, "%s\n", string_builder.ToString().Utf8().data()); } -#endif // NDEBUG +#endif // DCHECK_IS_ON() } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_break_token.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_break_token.h index f7f77622110..53c7163688b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_break_token.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_break_token.h @@ -58,10 +58,10 @@ class CORE_EXPORT NGBreakToken : public RefCounted<NGBreakToken> { box_, static_cast<NGLayoutInputNode::NGLayoutInputNodeType>(type_)); } -#ifndef NDEBUG +#if DCHECK_IS_ON() virtual String ToString() const; void ShowBreakTokenTree() const; -#endif // NDEBUG +#endif protected: NGBreakToken(NGBreakTokenType type, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc index 911faa1e8c2..147dc0495f2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc @@ -5,11 +5,13 @@ #include "third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h" #include <algorithm> -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" +#include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" @@ -21,87 +23,90 @@ namespace { inline bool NeedsColumnBalancing(LayoutUnit block_size, const ComputedStyle& style) { - return block_size == NGSizeIndefinite || + return block_size == kIndefiniteSize || style.GetColumnFill() == EColumnFill::kBalance; } -// Constrain a balanced column block size to not overflow the multicol -// container. -LayoutUnit ConstrainColumnBlockSize(LayoutUnit size, - NGBlockNode node, - const NGConstraintSpace& space) { - // The {,max-}{height,width} properties are specified on the multicol - // container, but here we're calculating the column block sizes inside the - // multicol container, which isn't exactly the same. We may shrink the column - // block size here, but we'll never stretch it, because the value passed is - // the perfect balanced block size. Making it taller would only disrupt the - // balanced output, for no reason. The only thing we need to worry about here - // is to not overflow the multicol container. - - // First of all we need to convert the size to a value that can be compared - // against the resolved properties on the multicol container. That means that - // we have to convert the value from content-box to border-box. - NGBoxStrut border_scrollbar_padding = - CalculateBorderScrollbarPadding(space, node); - LayoutUnit extra = border_scrollbar_padding.BlockSum(); - size += extra; - - NGBoxStrut border_padding = - ComputeBorders(space, node) + ComputePadding(space, node.Style()); - - const ComputedStyle& style = node.Style(); - LayoutUnit max = ResolveMaxBlockLength(space, style, border_padding, - style.LogicalMaxHeight(), size, - LengthResolvePhase::kLayout); - LayoutUnit extent = ResolveMainBlockLength(space, style, border_padding, - style.LogicalHeight(), size, - LengthResolvePhase::kLayout); - if (extent != NGSizeIndefinite) { - // A specified height/width will just constrain the maximum length. - max = std::min(max, extent); - } - - // Constrain and convert the value back to content-box. - size = std::min(size, max); - return size - extra; -} - } // namespace NGColumnLayoutAlgorithm::NGColumnLayoutAlgorithm( - NGBlockNode node, - const NGConstraintSpace& space, - const NGBreakToken* break_token) - : NGLayoutAlgorithm(node, space, To<NGBlockBreakToken>(break_token)) { - container_builder_.SetIsNewFormattingContext(space.IsNewFormattingContext()); + const NGLayoutAlgorithmParams& params) + : NGLayoutAlgorithm(params), + border_padding_(params.fragment_geometry.border + + params.fragment_geometry.padding), + border_scrollbar_padding_(border_padding_ + + params.fragment_geometry.scrollbar) { + container_builder_.SetIsNewFormattingContext( + params.space.IsNewFormattingContext()); + container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); } scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { - // TODO(layout-dev): Store some combination of border, scrollbar, padding on - // this class. - NGBoxStrut borders = ComputeBorders(ConstraintSpace(), Node()); - NGBoxStrut scrollbars = Node().GetScrollbarSizes(); - NGBoxStrut padding = ComputePadding(ConstraintSpace(), Style()) + - ComputeIntrinsicPadding(ConstraintSpace(), Node()); - NGBoxStrut border_scrollbar_padding = borders + scrollbars + padding; - NGLogicalSize border_box_size = - CalculateBorderBoxSize(ConstraintSpace(), Node(), borders + padding); - NGLogicalSize content_box_size = - ShrinkAvailableSize(border_box_size, border_scrollbar_padding); - NGLogicalSize column_size = CalculateColumnSize(content_box_size); + LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); + LogicalSize content_box_size = + ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); + LogicalSize column_size = CalculateColumnSize(content_box_size); WritingMode writing_mode = ConstraintSpace().GetWritingMode(); - LayoutUnit column_block_offset(border_scrollbar_padding.block_start); + + // Omit leading border+padding+scrollbar for all fragments but the first. + LayoutUnit column_block_offset = IsResumingLayout(BreakToken()) + ? LayoutUnit() + : border_scrollbar_padding_.block_start; + + // Figure out how much space we've already been able to process in previous + // fragments, if this multicol container participates in an outer + // fragmentation context. + LayoutUnit previously_used_block_size; + if (const auto* token = BreakToken()) + previously_used_block_size = token->UsedBlockSize(); + + bool needs_more_fragments_in_outer = false; + if (ConstraintSpace().HasBlockFragmentation()) { + // Subtract the space for content we've processed in previous fragments. + column_size.block_size -= previously_used_block_size; + + // Check if we can fit everything (that's remaining), block-wise, within the + // current outer fragmentainer. If we can't, we need to adjust the block + // size, and allow the multicol container to continue in a subsequent outer + // fragmentainer. + LayoutUnit available_outer_space = + ConstraintSpace().FragmentainerSpaceAtBfcStart() - column_block_offset; + if (column_size.block_size > available_outer_space) { + column_size.block_size = available_outer_space; + needs_more_fragments_in_outer = true; + } + } LayoutUnit column_inline_progression = column_size.inline_size + ResolveUsedColumnGap(content_box_size.inline_size, Style()); int used_column_count = ResolveUsedColumnCount(content_box_size.inline_size, Style()); + if (ConstraintSpace().HasBlockFragmentation()) + container_builder_.SetHasBlockFragmentation(); + do { - scoped_refptr<const NGBlockBreakToken> break_token = BreakToken(); + scoped_refptr<const NGBlockBreakToken> break_token; + if (const auto* token = BreakToken()) { + // We're resuming layout of this multicol container after an outer + // fragmentation break. Resume at the break token of the last column that + // we were able to lay out. Note that in some cases, there may be no child + // break tokens. That happens if we weren't able to lay out anything at + // all in the previous outer fragmentainer, e.g. due to a forced break + // before this multicol container, or e.g. if there was leading + // unbreakable content that couldn't fit in the space we were offered back + // then. In other words, in that case, we're about to create the first + // fragment for this multicol container. + const auto child_tokens = token->ChildBreakTokens(); + if (child_tokens.size()) { + break_token = + To<NGBlockBreakToken>(child_tokens[child_tokens.size() - 1]); + } + } + LayoutUnit intrinsic_block_size; - LayoutUnit column_inline_offset(border_scrollbar_padding.inline_start); + LayoutUnit column_inline_offset(border_scrollbar_padding_.inline_start); int actual_column_count = 0; int forced_break_count = 0; @@ -119,15 +124,17 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { NGConstraintSpace child_space = CreateConstraintSpaceForColumns( column_size, separate_leading_margins); - NGBlockLayoutAlgorithm child_algorithm(Node(), child_space, - break_token.get()); + NGFragmentGeometry fragment_geometry = + CalculateInitialFragmentGeometry(child_space, Node()); + + NGBlockLayoutAlgorithm child_algorithm( + {Node(), fragment_geometry, child_space, break_token.get()}); child_algorithm.SetBoxType(NGPhysicalFragment::kColumnBox); scoped_refptr<const NGLayoutResult> result = child_algorithm.Layout(); - const auto* column = - To<NGPhysicalBoxFragment>(result->PhysicalFragment()); + const auto& column = result->PhysicalFragment(); - NGLogicalOffset logical_offset(column_inline_offset, column_block_offset); - container_builder_.AddChild(*result, logical_offset); + LogicalOffset logical_offset(column_inline_offset, column_block_offset); + container_builder_.AddChild(column, logical_offset); LayoutUnit space_shortage = result->MinimalSpaceShortage(); if (space_shortage > LayoutUnit()) { @@ -142,16 +149,51 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { separate_leading_margins = false; } - LayoutUnit block_size = - NGBoxFragment(writing_mode, ConstraintSpace().Direction(), *column) - .BlockSize(); + LayoutUnit block_size = NGFragment(writing_mode, column).BlockSize(); intrinsic_block_size = std::max(intrinsic_block_size, column_block_offset + block_size); column_inline_offset += column_inline_progression; - break_token = To<NGBlockBreakToken>(column->BreakToken()); + break_token = To<NGBlockBreakToken>(column.BreakToken()); + + // If we're participating in an outer fragmentation context, we'll only + // allow as many columns as the used value of column-count, so that we + // don't overflow in the inline direction. There's one important + // exception: If we have determined that this is going to be the last + // fragment for this multicol container in the outer fragmentation + // context, we'll just allow as many columns as needed (and let them + // overflow in the inline direction, if necessary). We're not going to + // progress into a next outer fragmentainer if the (remaining part of the) + // multicol container fits block-wise in the current outer fragmentainer. + if (ConstraintSpace().HasBlockFragmentation() && break_token && + !break_token->IsFinished() && + actual_column_count >= used_column_count && + needs_more_fragments_in_outer) { + LayoutUnit fragment_block_size = child_space.FragmentainerBlockSize(); + // Calculate how much block space we've been able to process so far, in + // this fragment and all previous fragments generated for this multicol + // container. + LayoutUnit used_block_size = fragment_block_size; + // If this isn't the first fragment, add the amount that we were able to + // process in previous fragments. Otherwise, we're the first fragment, + // and we have to add leading border+padding+scrollbar to the fragment + // size (which would otherwise only be the size of the columns), since + // that's put at the start of the first fragment. + if (previously_used_block_size) + used_block_size += previously_used_block_size; + else + fragment_block_size += border_scrollbar_padding_.block_start; + container_builder_.SetUsedBlockSize(used_block_size); + container_builder_.SetBlockSize(fragment_block_size); + container_builder_.SetDidBreak(); + break; + } } while (break_token && !break_token->IsFinished()); + // TODO(mstensho): Nested column balancing. + if (container_builder_.DidBreak()) + break; + // If we overflowed (actual column count larger than what we have room for), // and we're supposed to calculate the column lengths automatically (column // balancing), see if we're able to stretch them. @@ -180,21 +222,30 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { break; } while (true); - NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders + scrollbars, - &container_builder_) - .Run(); - // TODO(mstensho): Propagate baselines. - if (border_box_size.block_size == NGSizeIndefinite) { - // Get the block size from the columns if it's auto. - border_box_size.block_size = - column_size.block_size + border_scrollbar_padding.BlockSum(); + // If we need another fragment for this multicol container (because we're + // nested inside another fragmentation context), we have already calculated + // the block size correctly. Otherwise, do it now. + if (!container_builder_.DidBreak()) { + LayoutUnit block_size; + if (border_box_size.block_size == kIndefiniteSize) { + // Get the block size from the columns if it's auto. + block_size = + column_size.block_size + border_scrollbar_padding_.BlockSum(); + } else { + // TODO(mstensho): end border and padding may overflow the parent + // fragmentainer, and we should avoid that. + block_size = border_box_size.block_size - previously_used_block_size; + } + container_builder_.SetBlockSize(block_size); } - container_builder_.SetInlineSize(border_box_size.inline_size); - container_builder_.SetBlockSize(border_box_size.block_size); - container_builder_.SetBorders(ComputeBorders(ConstraintSpace(), Node())); - container_builder_.SetPadding(ComputePadding(ConstraintSpace(), Style())); + + NGOutOfFlowLayoutPart( + Node(), ConstraintSpace(), + container_builder_.Borders() + container_builder_.Scrollbar(), + &container_builder_) + .Run(); return container_builder_.ToBoxFragment(); } @@ -202,7 +253,10 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { base::Optional<MinMaxSize> NGColumnLayoutAlgorithm::ComputeMinMaxSize( const MinMaxSizeInput& input) const { // First calculate the min/max sizes of columns. - NGBlockLayoutAlgorithm algorithm(Node(), ConstraintSpace()); + NGConstraintSpace space = CreateConstraintSpaceForMinMax(); + NGFragmentGeometry fragment_geometry = + CalculateInitialMinMaxFragmentGeometry(space, Node()); + NGBlockLayoutAlgorithm algorithm({Node(), fragment_geometry, space}); MinMaxSizeInput child_input(input); child_input.size_type = NGMinMaxSizeType::kContentBoxSize; base::Optional<MinMaxSize> min_max_sizes = @@ -228,17 +282,15 @@ base::Optional<MinMaxSize> NGColumnLayoutAlgorithm::ComputeMinMaxSize( sizes += column_gap * (column_count - 1); if (input.size_type == NGMinMaxSizeType::kBorderBoxSize) { - LayoutUnit border_scrollbar_padding = - CalculateBorderScrollbarPadding(ConstraintSpace(), node_).InlineSum(); - sizes += border_scrollbar_padding; + sizes += border_scrollbar_padding_.InlineSum(); } return sizes; } -NGLogicalSize NGColumnLayoutAlgorithm::CalculateColumnSize( - const NGLogicalSize& content_box_size) { - NGLogicalSize column_size = content_box_size; +LogicalSize NGColumnLayoutAlgorithm::CalculateColumnSize( + const LogicalSize& content_box_size) { + LogicalSize column_size = content_box_size; DCHECK_GE(column_size.inline_size, LayoutUnit()); column_size.inline_size = ResolveUsedColumnInlineSize(column_size.inline_size, Style()); @@ -254,7 +306,7 @@ NGLogicalSize NGColumnLayoutAlgorithm::CalculateColumnSize( } LayoutUnit NGColumnLayoutAlgorithm::CalculateBalancedColumnBlockSize( - const NGLogicalSize& column_size, + const LogicalSize& column_size, int column_count) { // To calculate a balanced column size, we need to figure out how tall our // content is. To do that we need to lay out. Create a special constraint @@ -262,14 +314,18 @@ LayoutUnit NGColumnLayoutAlgorithm::CalculateBalancedColumnBlockSize( // make us lay out all the multicol content as one single tall strip. When // we're done with this layout pass, we can examine the result and calculate // an ideal column block size. - NGConstraintSpace space = CreateConstaintSpaceForBalancing(column_size); - NGBlockLayoutAlgorithm balancing_algorithm(Node(), space); + NGConstraintSpace space = CreateConstraintSpaceForBalancing(column_size); + NGFragmentGeometry fragment_geometry = + CalculateInitialFragmentGeometry(space, Node()); + NGBlockLayoutAlgorithm balancing_algorithm( + {Node(), fragment_geometry, space}); scoped_refptr<const NGLayoutResult> result = balancing_algorithm.Layout(); // TODO(mstensho): This is where the fun begins. We need to examine the entire // fragment tree, not just the root. - NGFragment fragment(space.GetWritingMode(), *result->PhysicalFragment()); - LayoutUnit single_strip_block_size = fragment.BlockSize(); + LayoutUnit single_strip_block_size = + NGFragment(space.GetWritingMode(), result->PhysicalFragment()) + .BlockSize(); // Some extra care is required the division here. We want a the resulting // LayoutUnit value to be large enough to prevent overflowing columns. Use @@ -280,7 +336,7 @@ LayoutUnit NGColumnLayoutAlgorithm::CalculateBalancedColumnBlockSize( single_strip_block_size.ToFloat() / static_cast<float>(column_count)); // Finally, honor {,min-,max-}{height,width} properties. - return ConstrainColumnBlockSize(block_size, Node(), ConstraintSpace()); + return ConstrainColumnBlockSize(block_size); } LayoutUnit NGColumnLayoutAlgorithm::StretchColumnBlockSize( @@ -291,11 +347,46 @@ LayoutUnit NGColumnLayoutAlgorithm::StretchColumnBlockSize( return current_column_size; LayoutUnit length = current_column_size + minimal_space_shortage; // Honor {,min-,max-}{height,width} properties. - return ConstrainColumnBlockSize(length, Node(), ConstraintSpace()); + return ConstrainColumnBlockSize(length); +} + +// Constrain a balanced column block size to not overflow the multicol +// container. +LayoutUnit NGColumnLayoutAlgorithm::ConstrainColumnBlockSize( + LayoutUnit size) const { + // The {,max-}{height,width} properties are specified on the multicol + // container, but here we're calculating the column block sizes inside the + // multicol container, which isn't exactly the same. We may shrink the column + // block size here, but we'll never stretch it, because the value passed is + // the perfect balanced block size. Making it taller would only disrupt the + // balanced output, for no reason. The only thing we need to worry about here + // is to not overflow the multicol container. + + // First of all we need to convert the size to a value that can be compared + // against the resolved properties on the multicol container. That means that + // we have to convert the value from content-box to border-box. + LayoutUnit extra = border_scrollbar_padding_.BlockSum(); + size += extra; + + const ComputedStyle& style = Style(); + LayoutUnit max = ResolveMaxBlockLength( + ConstraintSpace(), style, border_padding_, style.LogicalMaxHeight(), size, + LengthResolvePhase::kLayout); + LayoutUnit extent = ResolveMainBlockLength( + ConstraintSpace(), style, border_padding_, style.LogicalHeight(), size, + LengthResolvePhase::kLayout); + if (extent != kIndefiniteSize) { + // A specified height/width will just constrain the maximum length. + max = std::min(max, extent); + } + + // Constrain and convert the value back to content-box. + size = std::min(size, max); + return size - extra; } NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForColumns( - const NGLogicalSize& column_size, + const LogicalSize& column_size, bool separate_leading_margins) const { NGConstraintSpaceBuilder space_builder( ConstraintSpace(), Style().GetWritingMode(), /* is_new_fc */ true); @@ -321,11 +412,11 @@ NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForColumns( return space_builder.ToConstraintSpace(); } -NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstaintSpaceForBalancing( - const NGLogicalSize& column_size) const { +NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForBalancing( + const LogicalSize& column_size) const { NGConstraintSpaceBuilder space_builder( ConstraintSpace(), Style().GetWritingMode(), /* is_new_fc */ true); - space_builder.SetAvailableSize({column_size.inline_size, NGSizeIndefinite}); + space_builder.SetAvailableSize({column_size.inline_size, kIndefiniteSize}); space_builder.SetPercentageResolutionSize(column_size); space_builder.SetIsAnonymous(true); space_builder.SetIsIntermediateLayout(true); @@ -333,4 +424,14 @@ NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstaintSpaceForBalancing( return space_builder.ToConstraintSpace(); } +NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForMinMax() + const { + NGConstraintSpaceBuilder space_builder( + ConstraintSpace(), Style().GetWritingMode(), /* is_new_fc */ true); + space_builder.SetIsAnonymous(true); + space_builder.SetIsIntermediateLayout(true); + + return space_builder.ToConstraintSpace(); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h index d303bb1406b..ae6905dfb6f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h @@ -12,18 +12,15 @@ namespace blink { class NGBlockNode; class NGBlockBreakToken; -class NGBreakToken; class NGConstraintSpace; -struct NGLogicalSize; +struct LogicalSize; class CORE_EXPORT NGColumnLayoutAlgorithm : public NGLayoutAlgorithm<NGBlockNode, NGBoxFragmentBuilder, NGBlockBreakToken> { public: - NGColumnLayoutAlgorithm(NGBlockNode node, - const NGConstraintSpace& space, - const NGBreakToken* break_token = nullptr); + NGColumnLayoutAlgorithm(const NGLayoutAlgorithmParams& params); scoped_refptr<const NGLayoutResult> Layout() override; @@ -31,8 +28,8 @@ class CORE_EXPORT NGColumnLayoutAlgorithm const MinMaxSizeInput&) const override; private: - NGLogicalSize CalculateColumnSize(const NGLogicalSize& content_box_size); - LayoutUnit CalculateBalancedColumnBlockSize(const NGLogicalSize& column_size, + LogicalSize CalculateColumnSize(const LogicalSize& content_box_size); + LayoutUnit CalculateBalancedColumnBlockSize(const LogicalSize& column_size, int column_count); // Stretch the column length, if allowed. We do this during column balancing, @@ -43,11 +40,17 @@ class CORE_EXPORT NGColumnLayoutAlgorithm LayoutUnit current_column_size, LayoutUnit container_content_box_block_size) const; + LayoutUnit ConstrainColumnBlockSize(LayoutUnit size) const; + NGConstraintSpace CreateConstraintSpaceForColumns( - const NGLogicalSize& column_size, + const LogicalSize& column_size, bool separate_leading_margins) const; - NGConstraintSpace CreateConstaintSpaceForBalancing( - const NGLogicalSize& column_size) const; + NGConstraintSpace CreateConstraintSpaceForBalancing( + const LogicalSize& column_size) const; + NGConstraintSpace CreateConstraintSpaceForMinMax() const; + + const NGBoxStrut border_padding_; + const NGBoxStrut border_scrollbar_padding_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc index 912dcbc233f..d2d3a04034a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc @@ -7,6 +7,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.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" namespace blink { @@ -28,21 +29,12 @@ class NGColumnLayoutAlgorithmTest : public NGBaseLayoutAlgorithmTest { } scoped_refptr<const NGPhysicalBoxFragment> RunBlockLayoutAlgorithm( - const NGConstraintSpace& space, - NGBlockNode node) { - scoped_refptr<const NGLayoutResult> result = - NGBlockLayoutAlgorithm(node, space).Layout(); - - return To<NGPhysicalBoxFragment>(result->PhysicalFragment()); - } - - scoped_refptr<const NGPhysicalBoxFragment> RunBlockLayoutAlgorithm( Element* element) { NGBlockNode container(ToLayoutBox(element->GetLayoutObject())); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite)); - return RunBlockLayoutAlgorithm(space, container); + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); + return NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(container, space); } String DumpFragmentTree(const NGPhysicalBoxFragment* fragment) { @@ -82,20 +74,20 @@ TEST_F(NGColumnLayoutAlgorithmTest, EmptyMulticol) { NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite)); + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); scoped_refptr<const NGPhysicalBoxFragment> parent_fragment = - RunBlockLayoutAlgorithm(space, container); + NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(container, space); FragmentChildIterator iterator(parent_fragment.get()); const auto* fragment = iterator.NextChild(); ASSERT_TRUE(fragment); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(210), LayoutUnit(100)), fragment->Size()); + EXPECT_EQ(PhysicalSize(210, 100), fragment->Size()); EXPECT_FALSE(iterator.NextChild()); // There should be nothing inside the multicol container. // TODO(mstensho): Get rid of this column fragment. It shouldn't be here. fragment = FragmentChildIterator(fragment).NextChild(); ASSERT_TRUE(fragment); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(100), LayoutUnit()), fragment->Size()); + EXPECT_EQ(PhysicalSize(100, 0), fragment->Size()); EXPECT_EQ(0UL, fragment->Children().size()); EXPECT_FALSE(iterator.NextChild()); @@ -123,30 +115,30 @@ TEST_F(NGColumnLayoutAlgorithmTest, EmptyBlock) { NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite)); + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); scoped_refptr<const NGPhysicalBoxFragment> parent_fragment = - RunBlockLayoutAlgorithm(space, container); + NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(container, space); FragmentChildIterator iterator(parent_fragment.get()); const auto* fragment = iterator.NextChild(); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(210), LayoutUnit(100)), fragment->Size()); + EXPECT_EQ(PhysicalSize(210, 100), fragment->Size()); ASSERT_TRUE(fragment); EXPECT_FALSE(iterator.NextChild()); iterator.SetParent(fragment); // first column fragment - NGPhysicalOffset offset; + PhysicalOffset offset; fragment = iterator.NextChild(&offset); ASSERT_TRUE(fragment); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(), LayoutUnit()), offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(100), LayoutUnit()), fragment->Size()); + EXPECT_EQ(PhysicalOffset(), offset); + EXPECT_EQ(PhysicalSize(100, 0), fragment->Size()); EXPECT_FALSE(iterator.NextChild()); // #child fragment in first column iterator.SetParent(fragment); fragment = iterator.NextChild(&offset); ASSERT_TRUE(fragment); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(), LayoutUnit()), offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(100), LayoutUnit()), fragment->Size()); + EXPECT_EQ(PhysicalOffset(), offset); + EXPECT_EQ(PhysicalSize(100, 0), fragment->Size()); EXPECT_EQ(0UL, fragment->Children().size()); EXPECT_FALSE(iterator.NextChild()); } @@ -172,31 +164,31 @@ TEST_F(NGColumnLayoutAlgorithmTest, BlockInOneColumn) { NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite)); + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); scoped_refptr<const NGPhysicalBoxFragment> parent_fragment = - RunBlockLayoutAlgorithm(space, container); + NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(container, space); FragmentChildIterator iterator(parent_fragment.get()); const auto* fragment = iterator.NextChild(); ASSERT_TRUE(fragment); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(310), LayoutUnit(100)), fragment->Size()); + EXPECT_EQ(PhysicalSize(310, 100), fragment->Size()); EXPECT_FALSE(iterator.NextChild()); iterator.SetParent(fragment); // first column fragment - NGPhysicalOffset offset; + PhysicalOffset offset; fragment = iterator.NextChild(&offset); ASSERT_TRUE(fragment); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(), LayoutUnit()), offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(150), LayoutUnit(100)), fragment->Size()); + EXPECT_EQ(PhysicalOffset(), offset); + EXPECT_EQ(PhysicalSize(150, 100), fragment->Size()); EXPECT_FALSE(iterator.NextChild()); // #child fragment in first column iterator.SetParent(fragment); fragment = iterator.NextChild(&offset); ASSERT_TRUE(fragment); - EXPECT_EQ(NGPhysicalOffset(LayoutUnit(), LayoutUnit()), offset); - EXPECT_EQ(NGPhysicalSize(LayoutUnit(90), LayoutUnit(100)), fragment->Size()); + EXPECT_EQ(PhysicalOffset(), offset); + EXPECT_EQ(PhysicalSize(90, 100), fragment->Size()); EXPECT_EQ(0UL, fragment->Children().size()); EXPECT_FALSE(iterator.NextChild()); } @@ -1985,8 +1977,10 @@ TEST_F(NGColumnLayoutAlgorithmTest, MinMax) { layout_object->SetStyle(style); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite)); - NGColumnLayoutAlgorithm algorithm(node, space); + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); + NGFragmentGeometry fragment_geometry = + CalculateInitialFragmentGeometry(space, node); + NGColumnLayoutAlgorithm algorithm({node, fragment_geometry, space}); base::Optional<MinMaxSize> size; MinMaxSizeInput zero_input( /* percentage_resolution_block_size */ (LayoutUnit())); @@ -2821,5 +2815,113 @@ TEST_F(NGColumnLayoutAlgorithmTest, ClassCBreakPointBeforeLine) { EXPECT_EQ(expectation, dump); } +TEST_F(NGColumnLayoutAlgorithmTest, Nested) { + SetBodyInnerHTML(R"HTML( + <style> + .outer { columns:3; height:50px; column-fill:auto; width:320px; } + .inner { columns:2; height:100px; column-fill:auto; padding:1px; } + .outer, .inner { column-gap:10px; } + .content { break-inside:avoid; height:20px; } + </style> + <div id="container"> + <div class="outer"> + <div class="content" style="width:5px;"></div> + <div class="inner"> + <div class="content" style="width:10px;"></div> + <div class="content" style="width:20px;"></div> + <div class="content" style="width:30px;"></div> + <div class="content" style="width:40px;"></div> + <div class="content" style="width:50px;"></div> + <div class="content" style="width:60px;"></div> + <div class="content" style="width:70px;"></div> + </div> + </div> + </div> + )HTML"); + + String dump = DumpFragmentTree(GetElementById("container")); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x50 + offset:0,0 size:320x50 + offset:0,0 size:100x50 + offset:0,0 size:5x20 + offset:0,20 size:100x30 + offset:1,1 size:44x29 + offset:0,0 size:10x20 + offset:55,1 size:44x29 + offset:0,0 size:20x20 + offset:110,0 size:100x50 + offset:0,0 size:100x50 + offset:1,0 size:44x50 + offset:0,0 size:30x20 + offset:0,20 size:40x20 + offset:55,0 size:44x50 + offset:0,0 size:50x20 + offset:0,20 size:60x20 + offset:220,0 size:100x23 + offset:0,0 size:100x23 + offset:1,0 size:44x20 + offset:0,0 size:70x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGColumnLayoutAlgorithmTest, NestedLimitedHeight) { + // This tests that we don't advance to the next outer fragmentainer when we've + // reached the bottom of an inner multicol container. We should create inner + // columns that overflow in the inline direction in that case. + SetBodyInnerHTML(R"HTML( + <style> + .outer { columns:2; height:50px; column-fill:auto; width:210px; } + .inner { columns:2; height:80px; column-fill:auto; } + .outer, .inner { column-gap:10px; } + .content { break-inside:avoid; height:20px; } + </style> + <div id="container"> + <div class="outer"> + <div class="content" style="width:5px;"></div> + <div class="inner"> + <div class="content" style="width:10px;"></div> + <div class="content" style="width:20px;"></div> + <div class="content" style="width:30px;"></div> + <div class="content" style="width:40px;"></div> + <div class="content" style="width:50px;"></div> + <div class="content" style="width:60px;"></div> + <div class="content" style="width:70px;"></div> + <div class="content" style="width:80px;"></div> + <div class="content" style="width:90px;"></div> + </div> + </div> + </div> + )HTML"); + + String dump = DumpFragmentTree(GetElementById("container")); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x50 + offset:0,0 size:210x50 + offset:0,0 size:100x50 + offset:0,0 size:5x20 + offset:0,20 size:100x30 + offset:0,0 size:45x30 + offset:0,0 size:10x20 + offset:55,0 size:45x30 + offset:0,0 size:20x20 + offset:110,0 size:100x50 + offset:0,0 size:100x50 + offset:0,0 size:45x50 + offset:0,0 size:30x20 + offset:0,20 size:40x20 + offset:55,0 size:45x50 + offset:0,0 size:50x20 + offset:0,20 size:60x20 + offset:110,0 size:45x50 + offset:0,0 size:70x20 + offset:0,20 size:80x20 + offset:165,0 size:45x20 + offset:0,0 size:90x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + } // anonymous namespace } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc index 6442fffe033..bc8d8bbb5f7 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc @@ -19,7 +19,7 @@ namespace blink { namespace { struct SameSizeAsNGConstraintSpace { - NGLogicalSize available_size; + LogicalSize available_size; union { NGBfcOffset bfc_offset; void* rare_data; @@ -44,9 +44,9 @@ NGConstraintSpace NGConstraintSpace::CreateFromLayoutObject( LayoutBoxUtils::AvailableLogicalWidth(block, cb); LayoutUnit available_logical_height = LayoutBoxUtils::AvailableLogicalHeight(block, cb); - NGLogicalSize percentage_size = {available_logical_width, - available_logical_height}; - NGLogicalSize available_size = percentage_size; + LogicalSize percentage_size = {available_logical_width, + available_logical_height}; + LogicalSize available_size = percentage_size; bool fixed_inline = false, fixed_block = false; bool fixed_block_is_definite = true; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h index fd52237443d..0464c66e683 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h @@ -7,11 +7,11 @@ #include "base/optional.h" #include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_size.h" #include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" #include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h" #include "third_party/blink/renderer/platform/text/text_direction.h" @@ -60,20 +60,16 @@ class CORE_EXPORT NGConstraintSpace final { public: enum ConstraintSpaceFlags { kOrthogonalWritingModeRoot = 1 << 0, - kFixedSizeInline = 1 << 1, - kFixedSizeBlock = 1 << 2, - kFixedSizeBlockIsDefinite = 1 << 3, - kShrinkToFit = 1 << 4, - kIntermediateLayout = 1 << 5, - kSeparateLeadingFragmentainerMargins = 1 << 6, - kNewFormattingContext = 1 << 7, - kAnonymous = 1 << 8, - kUseFirstLineStyle = 1 << 9, - kForceClearance = 1 << 10, - kHasRareData = 1 << 11, + kFixedSizeBlockIsDefinite = 1 << 1, + kIntermediateLayout = 1 << 2, + kSeparateLeadingFragmentainerMargins = 1 << 3, + kNewFormattingContext = 1 << 4, + kAnonymous = 1 << 5, + kUseFirstLineStyle = 1 << 6, + kAncestorHasClearancePastAdjoiningFloats = 1 << 7, // Size of bitfield used to store the flags. - kNumberOfConstraintSpaceFlags = 12 + kNumberOfConstraintSpaceFlags = 8 }; // To ensure that the bfc_offset_, rare_data_ union doesn't get polluted, @@ -154,7 +150,7 @@ class CORE_EXPORT NGConstraintSpace final { // The available space size. // See: https://drafts.csswg.org/css-sizing/#available - NGLogicalSize AvailableSize() const { return available_size_; } + LogicalSize AvailableSize() const { return available_size_; } // The size to use for percentage resolution. // See: https://drafts.csswg.org/css-sizing/#percentage-sizing @@ -169,7 +165,7 @@ class CORE_EXPORT NGConstraintSpace final { case kZero: return LayoutUnit(); case kIndefinite: - return NGSizeIndefinite; + return kIndefiniteSize; case kRareDataPercentage: DCHECK(HasRareData()); return rare_data_->percentage_resolution_size.inline_size; @@ -187,14 +183,14 @@ class CORE_EXPORT NGConstraintSpace final { case kZero: return LayoutUnit(); case kIndefinite: - return NGSizeIndefinite; + return kIndefiniteSize; case kRareDataPercentage: DCHECK(HasRareData()); return rare_data_->percentage_resolution_size.block_size; } } - NGLogicalSize PercentageResolutionSize() const { + LogicalSize PercentageResolutionSize() const { return {PercentageResolutionInlineSize(), PercentageResolutionBlockSize()}; } @@ -210,7 +206,7 @@ class CORE_EXPORT NGConstraintSpace final { case kZero: return LayoutUnit(); case kIndefinite: - return NGSizeIndefinite; + return kIndefiniteSize; case kRareDataPercentage: DCHECK(HasRareData()); return rare_data_->replaced_percentage_resolution_block_size; @@ -222,7 +218,7 @@ class CORE_EXPORT NGConstraintSpace final { } // The size to use for percentage resolution of replaced elements. - NGLogicalSize ReplacedPercentageResolutionSize() const { + LogicalSize ReplacedPercentageResolutionSize() const { return {ReplacedPercentageResolutionInlineSize(), ReplacedPercentageResolutionBlockSize()}; } @@ -233,7 +229,7 @@ class CORE_EXPORT NGConstraintSpace final { LayoutUnit PercentageResolutionInlineSizeForParentWritingMode() const { if (!IsOrthogonalWritingModeRoot()) return PercentageResolutionInlineSize(); - if (PercentageResolutionBlockSize() != NGSizeIndefinite) + if (PercentageResolutionBlockSize() != kIndefiniteSize) return PercentageResolutionBlockSize(); // TODO(mstensho): Figure out why we get here. It seems wrong, but we do get // here in some grid layout situations. @@ -242,7 +238,7 @@ class CORE_EXPORT NGConstraintSpace final { LayoutUnit FragmentainerBlockSize() const { return HasRareData() ? rare_data_->fragmentainer_block_size - : NGSizeIndefinite; + : kIndefiniteSize; } // Return the block space that was available in the current fragmentainer at @@ -252,7 +248,7 @@ class CORE_EXPORT NGConstraintSpace final { LayoutUnit FragmentainerSpaceAtBfcStart() const { DCHECK(HasBlockFragmentation()); return HasRareData() ? rare_data_->fragmentainer_space_at_bfc_start - : NGSizeIndefinite; + : kIndefiniteSize; } // Whether the current constraint space is for the newly established @@ -280,6 +276,16 @@ class CORE_EXPORT NGConstraintSpace final { // Also note this is true only when the document has ':first-line' rules. bool UseFirstLineStyle() const { return HasFlag(kUseFirstLineStyle); } + // Returns true if an ancestor had clearance past adjoining floats. + // + // Typically this can be detected by seeing if a |ForcedBfcBlockOffset| is + // set. However new formatting contexts may require additional passes (if + // margins are adjoining or not), and without this extra bit of information + // can get into a bad state. + bool AncestorHasClearancePastAdjoiningFloats() const { + return HasFlag(kAncestorHasClearancePastAdjoiningFloats); + } + // Some layout modes “stretch” their children to a fixed size (e.g. flex, // grid). These flags represented whether a layout needs to produce a // fragment that satisfies a fixed constraint in the inline and block @@ -287,9 +293,9 @@ class CORE_EXPORT NGConstraintSpace final { // // If these flags are true, the AvailableSize() is interpreted as the fixed // border-box size of this box in the respective dimension. - bool IsFixedSizeInline() const { return HasFlag(kFixedSizeInline); } + bool IsFixedSizeInline() const { return bitfields_.is_fixed_size_inline; } - bool IsFixedSizeBlock() const { return HasFlag(kFixedSizeBlock); } + bool IsFixedSizeBlock() const { return bitfields_.is_fixed_size_block; } // Whether a fixed block size should be considered definite. bool FixedSizeBlockIsDefinite() const { @@ -298,7 +304,7 @@ class CORE_EXPORT NGConstraintSpace final { // Whether an auto inline-size should be interpreted as shrink-to-fit // (ie. fit-content). This is used for inline-block, floats, etc. - bool IsShrinkToFit() const { return HasFlag(kShrinkToFit); } + bool IsShrinkToFit() const { return bitfields_.is_shrink_to_fit; } // Whether this constraint space is used for an intermediate layout in a // multi-pass layout. In such a case, we should not copy back the resulting @@ -326,6 +332,10 @@ class CORE_EXPORT NGConstraintSpace final { bitfields_.table_cell_child_layout_phase); } + bool IsInRestrictedBlockSizeTableCell() const { + return bitfields_.is_in_restricted_block_size_table_cell; + } + NGMarginStrut MarginStrut() const { return HasRareData() ? rare_data_->margin_strut : NGMarginStrut(); } @@ -350,27 +360,28 @@ class CORE_EXPORT NGConstraintSpace final { return HasRareData() ? rare_data_->bfc_offset : bfc_offset_; } - // If present, and the current layout hasn't resolved its BFC offset yet (see - // BfcOffset), the layout should position all of its unpositioned floats at - // this offset. This value is the BFC offset that we calculated in the - // previous pass, a pass which aborted once the BFC offset got resolved, - // because we had walked past content (i.e. floats) that depended on it being - // resolved. + // If present, and the current layout hasn't resolved its BFC block-offset + // yet (see BfcOffset), the layout should position all of its unpositioned + // floats at this offset. // - // This value should be propogated to child layouts if the current layout - // hasn't resolved its BFC offset yet. + // This value is present if: + // - An ancestor had clearance past adjoining floats. In this case this + // value is calculated ahead of time. + // - A second layout pass is required as there were unpositioned floats + // within the tree, and an arbitrary sibling determined their BFC + // block-offset. // - // This value is calculated *after* an initial pass of the tree, and should - // only be present during subsequent passes. - base::Optional<LayoutUnit> FloatsBfcBlockOffset() const { - return HasRareData() ? rare_data_->floats_bfc_block_offset : base::nullopt; + // This value should be propagated to child layouts if the current layout + // hasn't resolved its BFC offset yet. + base::Optional<LayoutUnit> ForcedBfcBlockOffset() const { + return HasRareData() ? rare_data_->forced_bfc_block_offset : base::nullopt; } // Return the types (none, left, right, both) of preceding adjoining // floats. These are floats that are added while the in-flow BFC offset is // still unknown. The floats may or may not be unpositioned (pending). That // depends on which layout pass we're in. They are typically positioned if - // FloatsBfcOffset() is known. Adjoining floats should be treated differently + // ForcedBfcOffset() is known. Adjoining floats should be treated differently // when calculating clearance on a block with adjoining block-start margin. // (in such cases we will know up front that the block will need clearance, // since, if it doesn't, the float will be pulled along with the block, and @@ -390,28 +401,6 @@ class CORE_EXPORT NGConstraintSpace final { return HasRareData() ? rare_data_->clearance_offset : LayoutUnit::Min(); } - // Return true if the fragment needs to have clearance applied to it, - // regardless of its hypothetical position. The fragment will then go exactly - // below the relevant floats. This happens when a cleared child gets separated - // from floats that would otherwise be adjoining; example: - // - // <div id="container"> - // <div id="float" style="float:left; width:100px; height:100px;"></div> - // <div id="clearee" style="clear:left; margin-top:12345px;">text</div> - // </div> - // - // Clearance separates #clearee from #container, and #float is positioned at - // the block-start content edge of #container. Without clearance, margins - // would have been adjoining and the large margin on #clearee would have - // pulled both #container and #float along with it. No margin, no matter how - // large, would ever be able to pull #clearee below the float then. But we - // have clearance, the margins are separated, and in this case we know that we - // have clearance even before we have laid out (because of the adjoining - // float). So it would just be wrong to check for clearance when we position - // #clearee. Nothing can prevent clearance here. A large margin on the cleared - // child will be canceled out with negative clearance. - bool ShouldForceClearance() const { return HasFlag(kForceClearance); } - const NGBaselineRequestList BaselineRequests() const { return NGBaselineRequestList(bitfields_.baseline_requests); } @@ -437,6 +426,12 @@ class CORE_EXPORT NGConstraintSpace final { return other.rare_data_->IsInitialForMaySkipLayout(); } + // Returns true if the size constraints (shrink-to-fit, fixed-inline-size) + // are equal. + bool AreSizeConstraintsEqual(const NGConstraintSpace& other) const { + return bitfields_.AreSizeConstraintsEqual(other.bitfields_); + } + bool AreSizesEqual(const NGConstraintSpace& other) const { if (available_size_ != other.available_size_) return false; @@ -509,23 +504,23 @@ class CORE_EXPORT NGConstraintSpace final { RareData(const RareData&) = default; ~RareData() = default; - NGLogicalSize percentage_resolution_size; + LogicalSize percentage_resolution_size; LayoutUnit replaced_percentage_resolution_block_size; NGBfcOffset bfc_offset; NGMarginStrut margin_strut; - base::Optional<LayoutUnit> floats_bfc_block_offset; + base::Optional<LayoutUnit> forced_bfc_block_offset; LayoutUnit clearance_offset = LayoutUnit::Min(); - LayoutUnit fragmentainer_block_size = NGSizeIndefinite; - LayoutUnit fragmentainer_space_at_bfc_start = NGSizeIndefinite; + LayoutUnit fragmentainer_block_size = kIndefiniteSize; + LayoutUnit fragmentainer_space_at_bfc_start = kIndefiniteSize; unsigned block_direction_fragmentation_type : 2; bool MaySkipLayout(const RareData& other) const { return margin_strut == other.margin_strut && - floats_bfc_block_offset == other.floats_bfc_block_offset && + forced_bfc_block_offset == other.forced_bfc_block_offset && fragmentainer_block_size == other.fragmentainer_block_size && fragmentainer_space_at_bfc_start == other.fragmentainer_space_at_bfc_start && @@ -536,9 +531,9 @@ class CORE_EXPORT NGConstraintSpace final { // Must be kept in sync with members checked within |MaySkipLayout|. bool IsInitialForMaySkipLayout() const { return margin_strut == NGMarginStrut() && - floats_bfc_block_offset == base::nullopt && - fragmentainer_block_size == NGSizeIndefinite && - fragmentainer_space_at_bfc_start == NGSizeIndefinite && + forced_bfc_block_offset == base::nullopt && + fragmentainer_block_size == kIndefiniteSize && + fragmentainer_space_at_bfc_start == kIndefiniteSize && block_direction_fragmentation_type == kFragmentNone; } }; @@ -550,33 +545,52 @@ class CORE_EXPORT NGConstraintSpace final { DISALLOW_NEW(); public: - // We explicitly define a default constructor to ensure the kHasRareData - // bitfield doesn't accidently get set. Bitfields() : Bitfields(WritingMode::kHorizontalTb) {} explicit Bitfields(WritingMode writing_mode) - : table_cell_child_layout_phase(static_cast<unsigned>( - NGTableCellChildLayoutPhase::kNotTableCellChild)), + : has_rare_data(false), adjoining_floats(static_cast<unsigned>(kFloatTypeNone)), writing_mode(static_cast<unsigned>(writing_mode)), direction(static_cast<unsigned>(TextDirection::kLtr)), + is_shrink_to_fit(false), + is_fixed_size_inline(false), + is_fixed_size_block(false), + is_in_restricted_block_size_table_cell(false), + table_cell_child_layout_phase(static_cast<unsigned>( + NGTableCellChildLayoutPhase::kNotTableCellChild)), flags(kFixedSizeBlockIsDefinite), percentage_inline_storage(kSameAsAvailable), percentage_block_storage(kSameAsAvailable), replaced_percentage_block_storage(kSameAsAvailable) {} bool MaySkipLayout(const Bitfields& other) const { - return table_cell_child_layout_phase == - other.table_cell_child_layout_phase && - adjoining_floats == other.adjoining_floats && + return adjoining_floats == other.adjoining_floats && writing_mode == other.writing_mode && flags == other.flags && baseline_requests == other.baseline_requests; } - unsigned table_cell_child_layout_phase : 2; // NGTableCellChildLayoutPhase - unsigned adjoining_floats : 2; // NGFloatTypes + bool AreSizeConstraintsEqual(const Bitfields& other) const { + return is_shrink_to_fit == other.is_shrink_to_fit && + is_fixed_size_inline == other.is_fixed_size_inline && + is_fixed_size_block == other.is_fixed_size_block && + is_in_restricted_block_size_table_cell == + other.is_in_restricted_block_size_table_cell && + table_cell_child_layout_phase == + other.table_cell_child_layout_phase; + } + + unsigned has_rare_data : 1; + unsigned adjoining_floats : 3; // NGFloatTypes unsigned writing_mode : 3; unsigned direction : 1; + + // Size constraints. + unsigned is_shrink_to_fit : 1; + unsigned is_fixed_size_inline : 1; + unsigned is_fixed_size_block : 1; + unsigned is_in_restricted_block_size_table_cell : 1; + unsigned table_cell_child_layout_phase : 2; // NGTableCellChildLayoutPhase + unsigned flags : kNumberOfConstraintSpaceFlags; // ConstraintSpaceFlags unsigned baseline_requests : NGBaselineRequestList::kSerializedBits; @@ -589,21 +603,21 @@ class CORE_EXPORT NGConstraintSpace final { return bitfields_.flags & static_cast<unsigned>(mask); } - inline bool HasRareData() const { return HasFlag(kHasRareData); } + inline bool HasRareData() const { return bitfields_.has_rare_data; } RareData* EnsureRareData() { if (!HasRareData()) { rare_data_ = new RareData(bfc_offset_); - bitfields_.flags |= kHasRareData; + bitfields_.has_rare_data = true; } return rare_data_; } - NGLogicalSize available_size_; + LogicalSize available_size_; // To save a little space, we union these two fields. rare_data_ is valid if - // the kHasRareData bitfield is set, otherwise bfc_offset_ is valid. + // the |has_rare_data| bit is set, otherwise bfc_offset_ is valid. union { NGBfcOffset bfc_offset_; RareData* rare_data_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.cc index 23eed9091e5..c3c57444888 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.cc @@ -15,7 +15,7 @@ NGPercentageStorage GetPercentageStorage(LayoutUnit percentage_size, if (percentage_size == available_size) return kSameAsAvailable; - if (percentage_size == NGSizeIndefinite) + if (percentage_size == kIndefiniteSize) return kIndefinite; if (percentage_size == LayoutUnit()) @@ -27,7 +27,7 @@ NGPercentageStorage GetPercentageStorage(LayoutUnit percentage_size, } // namespace NGConstraintSpaceBuilder& NGConstraintSpaceBuilder::SetPercentageResolutionSize( - NGLogicalSize percentage_resolution_size) { + LogicalSize percentage_resolution_size) { #if DCHECK_IS_ON() DCHECK(is_available_size_set_); #endif @@ -73,7 +73,7 @@ NGConstraintSpaceBuilder& NGConstraintSpaceBuilder::SetPercentageResolutionSize( NGConstraintSpaceBuilder& NGConstraintSpaceBuilder::SetReplacedPercentageResolutionSize( - NGLogicalSize replaced_percentage_resolution_size) { + LogicalSize replaced_percentage_resolution_size) { #if DCHECK_IS_ON() DCHECK(is_available_size_set_); #endif diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h index 41670b6f69b..09a7f0b5770 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h @@ -6,8 +6,8 @@ #define NGConstraintSpaceBuilder_h #include "base/optional.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h" #include "third_party/blink/renderer/platform/text/text_direction.h" @@ -63,20 +63,20 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { // https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto void AdjustInlineSizeIfNeeded(LayoutUnit* inline_size) const { DCHECK(!is_in_parallel_flow_); - if (*inline_size != NGSizeIndefinite) + if (*inline_size != kIndefiniteSize) return; - DCHECK_NE(orthogonal_fallback_inline_size_, NGSizeIndefinite); + DCHECK_NE(orthogonal_fallback_inline_size_, kIndefiniteSize); *inline_size = orthogonal_fallback_inline_size_; } - NGConstraintSpaceBuilder& SetAvailableSize(NGLogicalSize available_size) { + NGConstraintSpaceBuilder& SetAvailableSize(LogicalSize available_size) { #if DCHECK_IS_ON() is_available_size_set_ = true; #endif space_.available_size_ = available_size; if (UNLIKELY(!is_in_parallel_flow_)) { - space_.available_size_.Flip(); + space_.available_size_.Transpose(); AdjustInlineSizeIfNeeded(&space_.available_size_.inline_size); } @@ -84,10 +84,10 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { } NGConstraintSpaceBuilder& SetPercentageResolutionSize( - NGLogicalSize percentage_resolution_size); + LogicalSize percentage_resolution_size); NGConstraintSpaceBuilder& SetReplacedPercentageResolutionSize( - NGLogicalSize replaced_percentage_resolution_size); + LogicalSize replaced_percentage_resolution_size); // Set the fallback available inline-size for an orthogonal child. The size is // the inline size in the writing mode of the orthogonal child. @@ -101,7 +101,7 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { DCHECK(!is_fragmentainer_block_size_set_); is_fragmentainer_block_size_set_ = true; #endif - if (size != NGSizeIndefinite) + if (size != kIndefiniteSize) space_.EnsureRareData()->fragmentainer_block_size = size; return *this; } @@ -111,7 +111,7 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { DCHECK(!is_fragmentainer_space_at_bfc_start_set_); is_fragmentainer_space_at_bfc_start_set_ = true; #endif - if (space != NGSizeIndefinite) + if (space != kIndefiniteSize) space_.EnsureRareData()->fragmentainer_space_at_bfc_start = space; return *this; } @@ -123,18 +123,18 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { NGConstraintSpaceBuilder& SetIsFixedSizeInline(bool b) { if (LIKELY(is_in_parallel_flow_)) - SetFlag(NGConstraintSpace::kFixedSizeInline, b); + space_.bitfields_.is_fixed_size_inline = b; else - SetFlag(NGConstraintSpace::kFixedSizeBlock, b); + space_.bitfields_.is_fixed_size_block = b; return *this; } NGConstraintSpaceBuilder& SetIsFixedSizeBlock(bool b) { if (LIKELY(is_in_parallel_flow_)) - SetFlag(NGConstraintSpace::kFixedSizeBlock, b); + space_.bitfields_.is_fixed_size_block = b; else - SetFlag(NGConstraintSpace::kFixedSizeInline, b); + space_.bitfields_.is_fixed_size_inline = b; return *this; } @@ -147,7 +147,7 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { } NGConstraintSpaceBuilder& SetIsShrinkToFit(bool b) { - SetFlag(NGConstraintSpace::kShrinkToFit, b); + space_.bitfields_.is_shrink_to_fit = b; return *this; } @@ -184,6 +184,11 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { return *this; } + NGConstraintSpaceBuilder& SetAncestorHasClearancePastAdjoiningFloats() { + SetFlag(NGConstraintSpace::kAncestorHasClearancePastAdjoiningFloats, true); + return *this; + } + NGConstraintSpaceBuilder& SetAdjoiningFloatTypes(NGFloatTypes floats) { if (!is_new_fc_) space_.bitfields_.adjoining_floats = static_cast<unsigned>(floats); @@ -212,15 +217,15 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { return *this; } - NGConstraintSpaceBuilder& SetFloatsBfcBlockOffset( - const base::Optional<LayoutUnit>& floats_bfc_block_offset) { + NGConstraintSpaceBuilder& SetForcedBfcBlockOffset( + const base::Optional<LayoutUnit>& forced_bfc_block_offset) { #if DCHECK_IS_ON() - DCHECK(!is_floats_bfc_block_offset_set_); - is_floats_bfc_block_offset_set_ = true; + DCHECK(!is_forced_bfc_block_offset_set_); + is_forced_bfc_block_offset_set_ = true; #endif - if (LIKELY(!is_new_fc_ && floats_bfc_block_offset != base::nullopt)) { - space_.EnsureRareData()->floats_bfc_block_offset = - floats_bfc_block_offset; + if (LIKELY(!is_new_fc_ && forced_bfc_block_offset != base::nullopt)) { + space_.EnsureRareData()->forced_bfc_block_offset = + forced_bfc_block_offset; } return *this; @@ -237,11 +242,6 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { return *this; } - NGConstraintSpaceBuilder& SetShouldForceClearance(bool b) { - SetFlag(NGConstraintSpace::kForceClearance, b); - return *this; - } - NGConstraintSpaceBuilder& SetTableCellChildLayoutPhase( NGTableCellChildLayoutPhase table_cell_child_layout_phase) { space_.bitfields_.table_cell_child_layout_phase = @@ -249,6 +249,11 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { return *this; } + NGConstraintSpaceBuilder& SetIsInRestrictedBlockSizeTableCell() { + space_.bitfields_.is_in_restricted_block_size_table_cell = true; + return *this; + } + NGConstraintSpaceBuilder& SetExclusionSpace( const NGExclusionSpace& exclusion_space) { if (!is_new_fc_) @@ -300,7 +305,7 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { // Orthogonal writing mode roots may need a fallback, to prevent available // inline size from being indefinite, which isn't allowed. This is the // available inline size in the writing mode of the orthogonal child. - LayoutUnit orthogonal_fallback_inline_size_ = NGSizeIndefinite; + LayoutUnit orthogonal_fallback_inline_size_ = kIndefiniteSize; bool is_in_parallel_flow_; bool is_new_fc_; @@ -312,7 +317,7 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { bool is_fragmentainer_space_at_bfc_start_set_ = false; bool is_block_direction_fragmentation_type_set_ = false; bool is_margin_strut_set_ = false; - bool is_floats_bfc_block_offset_set_ = false; + bool is_forced_bfc_block_offset_set_ = false; bool is_clearance_offset_set_ = false; bool to_constraint_space_called_ = false; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder_test.cc index eb59619ae1d..0f853d9b508 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder_test.cc @@ -14,13 +14,13 @@ using NGConstraintSpaceBuilderTest = NGLayoutTest; // Asserts that indefinite inline length becomes initial containing // block width for horizontal-tb inside vertical document. TEST(NGConstraintSpaceBuilderTest, AvailableSizeFromHorizontalICB) { - NGPhysicalSize icb_size{NGSizeIndefinite, LayoutUnit(51)}; + PhysicalSize icb_size{kIndefiniteSize, LayoutUnit(51)}; NGConstraintSpaceBuilder horizontal_builder(WritingMode::kHorizontalTb, WritingMode::kHorizontalTb, /* is_new_fc */ true); - NGLogicalSize fixed_size{LayoutUnit(100), LayoutUnit(200)}; - NGLogicalSize indefinite_size{NGSizeIndefinite, NGSizeIndefinite}; + LogicalSize fixed_size{LayoutUnit(100), LayoutUnit(200)}; + LogicalSize indefinite_size{kIndefiniteSize, kIndefiniteSize}; horizontal_builder.SetOrthogonalFallbackInlineSize(icb_size.height); horizontal_builder.SetAvailableSize(fixed_size); @@ -43,13 +43,13 @@ TEST(NGConstraintSpaceBuilderTest, AvailableSizeFromHorizontalICB) { // Asserts that indefinite inline length becomes initial containing // block height for vertical-lr inside horizontal document. TEST(NGConstraintSpaceBuilderTest, AvailableSizeFromVerticalICB) { - NGPhysicalSize icb_size{LayoutUnit(51), NGSizeIndefinite}; + PhysicalSize icb_size{LayoutUnit(51), kIndefiniteSize}; NGConstraintSpaceBuilder horizontal_builder(WritingMode::kVerticalLr, WritingMode::kVerticalLr, /* is_new_fc */ true); - NGLogicalSize fixed_size{LayoutUnit(100), LayoutUnit(200)}; - NGLogicalSize indefinite_size{NGSizeIndefinite, NGSizeIndefinite}; + LogicalSize fixed_size{LayoutUnit(100), LayoutUnit(200)}; + LogicalSize indefinite_size{kIndefiniteSize, kIndefiniteSize}; horizontal_builder.SetOrthogonalFallbackInlineSize(icb_size.width); horizontal_builder.SetAvailableSize(fixed_size); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc index 39f44dbe3bb..e62ccbc378e 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc @@ -7,48 +7,61 @@ #include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.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_block_break_token.h" -#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" #include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/platform/text/writing_mode.h" namespace blink { +namespace { +// This routine returns true if inline_container should replace descendant's +// inline container. +bool IsInlineContainerForDescendant( + const NGOutOfFlowPositionedDescendant& descendant, + const LayoutObject* inline_container) { + return !descendant.inline_container && inline_container && + inline_container->IsLayoutInline() && + inline_container->CanContainOutOfFlowPositionedElement( + descendant.node.Style().GetPosition()); +} +} // namespace + NGContainerFragmentBuilder& NGContainerFragmentBuilder::AddChild( - const NGLayoutResult& child, - const NGLogicalOffset& child_offset) { + const NGPhysicalContainerFragment& child, + const LogicalOffset& child_offset, + const LayoutInline* inline_container) { // Collect the child's out of flow descendants. // child_offset is offset of inline_start/block_start vertex. // Candidates need offset of top/left vertex. - const auto& out_of_flow_descendants = child.OutOfFlowPositionedDescendants(); - if (!out_of_flow_descendants.IsEmpty()) { - NGLogicalOffset top_left_offset; - NGPhysicalSize child_size = child.PhysicalFragment()->Size(); + if (child.HasOutOfFlowPositionedDescendants()) { + const auto& out_of_flow_descendants = + child.OutOfFlowPositionedDescendants(); + LogicalOffset top_left_offset; + PhysicalSize child_size = child.Size(); switch (GetWritingMode()) { case WritingMode::kHorizontalTb: top_left_offset = (IsRtl(Direction())) - ? NGLogicalOffset{child_offset.inline_offset + child_size.width, - child_offset.block_offset} + ? LogicalOffset{child_offset.inline_offset + child_size.width, + child_offset.block_offset} : child_offset; break; case WritingMode::kVerticalRl: case WritingMode::kSidewaysRl: top_left_offset = (IsRtl(Direction())) - ? NGLogicalOffset{child_offset.inline_offset + - child_size.height, - child_offset.block_offset + child_size.width} - : NGLogicalOffset{child_offset.inline_offset, - child_offset.block_offset + child_size.width}; + ? LogicalOffset{child_offset.inline_offset + child_size.height, + child_offset.block_offset + child_size.width} + : LogicalOffset{child_offset.inline_offset, + child_offset.block_offset + child_size.width}; break; case WritingMode::kVerticalLr: case WritingMode::kSidewaysLr: - top_left_offset = (IsRtl(Direction())) - ? NGLogicalOffset{child_offset.inline_offset + - child_size.height, - child_offset.block_offset} - : child_offset; + top_left_offset = + (IsRtl(Direction())) + ? LogicalOffset{child_offset.inline_offset + child_size.height, + child_offset.block_offset} + : child_offset; break; } @@ -62,44 +75,62 @@ NGContainerFragmentBuilder& NGContainerFragmentBuilder::AddChild( // </div> // TODO(layout-dev): This code should eventually be removed once we handle // relative positioned objects directly in the fragment tree. - if (LayoutBox* child_box = - ToLayoutBoxOrNull(child.PhysicalFragment()->GetLayoutObject())) { - top_left_offset += - NGPhysicalOffset(child_box->OffsetForInFlowPosition()) - .ConvertToLogical(GetWritingMode(), Direction(), NGPhysicalSize(), - NGPhysicalSize()); + if (const LayoutBox* child_box = + ToLayoutBoxOrNull(child.GetLayoutObject())) { + top_left_offset += PhysicalOffset(child_box->OffsetForInFlowPosition()) + .ConvertToLogical(GetWritingMode(), Direction(), + PhysicalSize(), PhysicalSize()); } - for (const NGOutOfFlowPositionedDescendant& descendant : + for (NGOutOfFlowPositionedDescendant& descendant : out_of_flow_descendants) { + if (IsInlineContainerForDescendant(descendant, inline_container)) { + descendant.inline_container = inline_container; + } oof_positioned_candidates_.push_back( NGOutOfFlowPositionedCandidate(descendant, top_left_offset)); } } - if (child.HasOrthogonalFlowRoots()) + // For the |has_orthogonal_flow_roots_| flag, we don't care about the type of + // child (OOF-positioned, etc), it is for *any* descendant. + if (child.HasOrthogonalFlowRoots() || + !IsParallelWritingMode(child.Style().GetWritingMode(), + Style().GetWritingMode())) has_orthogonal_flow_roots_ = true; // We only need to report if inflow or floating elements depend on the // percentage resolution block-size. OOF-positioned children resolve their // percentages against the "final" size of their parent. - if (child.DependsOnPercentageBlockSize() && - !child.PhysicalFragment()->IsOutOfFlowPositioned()) - has_child_that_depends_on_percentage_block_size_ = true; - - if (child.MayHaveDescendantAboveBlockStart() && - !child.PhysicalFragment()->IsBlockFormattingContextRoot()) + if (child.DependsOnPercentageBlockSize() && !child.IsOutOfFlowPositioned()) + has_descendant_that_depends_on_percentage_block_size_ = true; + + // The |may_have_descendant_above_block_start_| flag is used to determine if + // a fragment can be re-used when floats are present. We only are about: + // - Inflow children who are positioned above our block-start edge. + // - Any inflow descendants (within the same formatting-context) that *may* + // be positioned above our block-start edge. + if ((child_offset.block_offset < LayoutUnit() && + !child.IsOutOfFlowPositioned()) || + (!child.IsBlockFormattingContextRoot() && + child.MayHaveDescendantAboveBlockStart())) may_have_descendant_above_block_start_ = true; - return AddChild(child.PhysicalFragment(), child_offset); -} + // Compute |has_floating_descendants_| to optimize tree traversal in paint. + if (!has_floating_descendants_) { + if (child.IsFloating()) { + has_floating_descendants_ = true; + } else { + if (!child.IsBlockFormattingContextRoot() && + child.HasFloatingDescendants()) + has_floating_descendants_ = true; + } + } -NGContainerFragmentBuilder& NGContainerFragmentBuilder::AddChild( - scoped_refptr<const NGPhysicalFragment> child, - const NGLogicalOffset& child_offset) { - NGBreakToken* child_break_token = child->BreakToken(); + // Collect any (block) break tokens. + NGBreakToken* child_break_token = child.BreakToken(); if (child_break_token && has_block_fragmentation_) { - switch (child->Type()) { + switch (child.Type()) { case NGPhysicalFragment::kFragmentBox: case NGPhysicalFragment::kFragmentRenderedLegend: if (To<NGBlockBreakToken>(child_break_token)->HasLastResortBreak()) @@ -119,61 +150,54 @@ NGContainerFragmentBuilder& NGContainerFragmentBuilder::AddChild( } } - // Compute |has_floating_descendants_| to optimize tree traversal in paint. - if (!has_floating_descendants_) { - if (child->IsFloating()) { - has_floating_descendants_ = true; - } else { - auto* child_container = DynamicTo<NGPhysicalContainerFragment>(*child); - if (child_container && !child->IsBlockFormattingContextRoot() && - child_container->HasFloatingDescendants()) - has_floating_descendants_ = true; - } - } - - if (child_offset.block_offset < LayoutUnit()) - may_have_descendant_above_block_start_ = true; + AddChildInternal(&child, child_offset); + return *this; +} - if (!IsParallelWritingMode(child->Style().GetWritingMode(), - Style().GetWritingMode())) - has_orthogonal_flow_roots_ = true; +void NGContainerFragmentBuilder::AddChildInternal( + scoped_refptr<const NGPhysicalFragment> child, + const LogicalOffset& child_offset) { + // In order to know where list-markers are within the children list (for the + // |NGSimplifiedLayoutAlgorithm|) we always place them as the first child. + if (child->IsListMarker()) { + children_.push_front(ChildWithOffset(child_offset, std::move(child))); + return; + } - children_.emplace_back(std::move(child)); - offsets_.push_back(child_offset); - return *this; + children_.emplace_back(child_offset, std::move(child)); } -NGLogicalOffset NGContainerFragmentBuilder::GetChildOffset( - const LayoutObject* child) const { - for (wtf_size_t i = 0; i < children_.size(); ++i) { - if (children_[i]->GetLayoutObject() == child) - return offsets_[i]; +LogicalOffset NGContainerFragmentBuilder::GetChildOffset( + const LayoutObject* object) const { + for (const auto& child : children_) { + if (child.fragment->GetLayoutObject() == object) + return child.offset; // TODO(layout-dev): ikilpatrick thinks we may need to traverse // further than the initial line-box children for a nested inline // container. We could not come up with a testcase, it would be // something with split inlines, and nested oof/fixed descendants maybe. - if (children_[i]->IsLineBox()) { + if (child.fragment->IsLineBox()) { const auto& line_box_fragment = - To<NGPhysicalLineBoxFragment>(*children_[i]); + To<NGPhysicalLineBoxFragment>(*child.fragment); for (const auto& line_box_child : line_box_fragment.Children()) { - if (line_box_child->GetLayoutObject() == child) { - return offsets_[i] + line_box_child.Offset().ConvertToLogical( - GetWritingMode(), Direction(), - line_box_fragment.Size(), - line_box_child->Size()); + if (line_box_child->GetLayoutObject() == object) { + return child.offset + line_box_child.Offset().ConvertToLogical( + GetWritingMode(), Direction(), + line_box_fragment.Size(), + line_box_child->Size()); } } } } NOTREACHED(); - return NGLogicalOffset(); + return LogicalOffset(); } NGContainerFragmentBuilder& NGContainerFragmentBuilder::AddOutOfFlowChildCandidate( NGBlockNode child, - const NGLogicalOffset& child_offset, + const LogicalOffset& child_offset, base::Optional<TextDirection> container_direction) { DCHECK(child); DCHECK(layout_object_ && !layout_object_->IsLayoutInline() || @@ -188,7 +212,7 @@ NGContainerFragmentBuilder::AddOutOfFlowChildCandidate( oof_positioned_candidates_.push_back(NGOutOfFlowPositionedCandidate( NGOutOfFlowPositionedDescendant( child, NGStaticPosition::Create(GetWritingMode(), direction, - NGPhysicalOffset())), + PhysicalOffset())), child_offset)); return *this; @@ -212,12 +236,11 @@ void NGContainerFragmentBuilder::GetAndClearOutOfFlowDescendantCandidates( DCHECK_GE(InlineSize(), LayoutUnit()); DCHECK_GE(BlockSize(), LayoutUnit()); - NGPhysicalSize builder_physical_size = - ToNGPhysicalSize(Size(), GetWritingMode()); + PhysicalSize builder_physical_size = ToPhysicalSize(Size(), GetWritingMode()); for (NGOutOfFlowPositionedCandidate& candidate : oof_positioned_candidates_) { - NGPhysicalOffset child_offset = candidate.child_offset.ConvertToPhysical( - GetWritingMode(), Direction(), builder_physical_size, NGPhysicalSize()); + PhysicalOffset child_offset = candidate.child_offset.ConvertToPhysical( + GetWritingMode(), Direction(), builder_physical_size, PhysicalSize()); NGStaticPosition builder_relative_position; builder_relative_position.type = candidate.descendant.static_position.type; @@ -230,21 +253,20 @@ void NGContainerFragmentBuilder::GetAndClearOutOfFlowDescendantCandidates( // // This checks if the object creating this box will be the container for // the given descendant. - const LayoutObject* inline_container = + const LayoutInline* inline_container = candidate.descendant.inline_container; - if (!inline_container && layout_object_ && - layout_object_->IsLayoutInline() && - layout_object_->CanContainOutOfFlowPositionedElement( - candidate.descendant.node.Style().GetPosition())) - inline_container = layout_object_; - + if (IsInlineContainerForDescendant(candidate.descendant, layout_object_)) { + inline_container = ToLayoutInline(layout_object_); + } descendant_candidates->push_back(NGOutOfFlowPositionedDescendant( candidate.descendant.node, builder_relative_position, - inline_container)); - NGLogicalOffset container_offset = + inline_container ? ToLayoutInline(inline_container->ContinuationRoot()) + : nullptr)); + + LogicalOffset container_offset = builder_relative_position.offset.ConvertToLogical( GetWritingMode(), Direction(), builder_physical_size, - NGPhysicalSize()); + PhysicalSize()); candidate.descendant.node.SaveStaticOffsetForLegacy(container_offset, current_container); } @@ -259,7 +281,7 @@ void NGContainerFragmentBuilder::GetAndClearOutOfFlowDescendantCandidates( oof_positioned_candidates_.Shrink(0); } -#ifndef NDEBUG +#if DCHECK_IS_ON() String NGContainerFragmentBuilder::ToString() const { StringBuilder builder; @@ -267,7 +289,7 @@ String NGContainerFragmentBuilder::ToString() const { InlineSize().ToFloat(), BlockSize().ToFloat(), children_.size()); for (auto& child : children_) { - builder.Append(child->DumpFragmentTree( + builder.Append(child.fragment->DumpFragmentTree( NGPhysicalFragment::DumpAll & ~NGPhysicalFragment::DumpHeaderText)); } return builder.ToString(); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h index 5db3e92b586..bd692e43df1 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h @@ -7,10 +7,11 @@ #include "base/memory/scoped_refptr.h" #include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_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_floats_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h" @@ -24,15 +25,24 @@ namespace blink { class NGExclusionSpace; -class NGLayoutResult; class NGPhysicalFragment; class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { - STACK_ALLOCATED(); + DISALLOW_NEW(); public: - typedef Vector<scoped_refptr<const NGPhysicalFragment>, 4> ChildrenVector; - typedef Vector<NGLogicalOffset, 4> OffsetVector; + struct ChildWithOffset { + DISALLOW_NEW(); + ChildWithOffset(LogicalOffset offset, + scoped_refptr<const NGPhysicalFragment> fragment) + : offset(offset), fragment(std::move(fragment)) {} + + // We store logical offsets (instead of the final physical), as we can't + // convert into the physical coordinate space until we know our final size. + LogicalOffset offset; + scoped_refptr<const NGPhysicalFragment> fragment; + }; + typedef Vector<ChildWithOffset, 4> ChildrenVector; LayoutUnit BfcLineOffset() const { return bfc_line_offset_; } NGContainerFragmentBuilder& SetBfcLineOffset(LayoutUnit bfc_line_offset) { @@ -40,8 +50,8 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { return *this; } - // The NGBfcOffset is where this fragment was positioned within the BFC. If - // it is not set, this fragment may be placed anywhere within the BFC. + // The BFC block-offset is where this fragment was positioned within the BFC. + // If it is not set, this fragment may be placed anywhere within the BFC. const base::Optional<LayoutUnit>& BfcBlockOffset() const { return bfc_block_offset_; } @@ -76,19 +86,23 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { return *this; } - NGContainerFragmentBuilder& AddChild(const NGLayoutResult&, - const NGLogicalOffset&); + NGContainerFragmentBuilder& AddChild( + const NGPhysicalContainerFragment&, + const LogicalOffset&, + const LayoutInline* inline_container = nullptr); - // This version of AddChild will not propagate floats/out_of_flow. - // Use the AddChild(NGLayoutResult) variant if NGLayoutResult is available. - NGContainerFragmentBuilder& AddChild(scoped_refptr<const NGPhysicalFragment>, - const NGLogicalOffset&); + NGContainerFragmentBuilder& AddChild( + scoped_refptr<const NGPhysicalTextFragment> child, + const LogicalOffset& offset) { + AddChildInternal(child, offset); + return *this; + } const ChildrenVector& Children() const { return children_; } // Returns offset for given child. DCHECK if child not found. // Warning: Do not call unless necessary. - NGLogicalOffset GetChildOffset(const LayoutObject* child) const; + LogicalOffset GetChildOffset(const LayoutObject* child) const; // Builder has non-trivial out-of-flow descendant methods. // These methods are building blocks for implementation of @@ -120,7 +134,7 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { // Pass in direction if candidates direction does not match. NGContainerFragmentBuilder& AddOutOfFlowChildCandidate( NGBlockNode, - const NGLogicalOffset& child_offset, + const LogicalOffset& child_offset, base::Optional<TextDirection> container_direction = base::nullopt); NGContainerFragmentBuilder& AddOutOfFlowDescendant( @@ -146,14 +160,17 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { nullptr); } + NGContainerFragmentBuilder& SetIsSelfCollapsing() { + is_self_collapsing_ = true; + return *this; + } + NGContainerFragmentBuilder& SetIsPushedByFloats() { is_pushed_by_floats_ = true; return *this; } bool IsPushedByFloats() const { return is_pushed_by_floats_; } - bool HasFloatingDescendants() const { return has_floating_descendants_; } - NGContainerFragmentBuilder& ResetAdjoiningFloatTypes() { adjoining_floats_ = kFloatTypeNone; return *this; @@ -162,6 +179,10 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { adjoining_floats_ |= floats; return *this; } + NGContainerFragmentBuilder& SetAdjoiningFloatTypes(NGFloatTypes floats) { + adjoining_floats_ = floats; + return *this; + } NGFloatTypes AdjoiningFloatTypes() const { return adjoining_floats_; } NGContainerFragmentBuilder& SetHasBlockFragmentation() { @@ -171,7 +192,7 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { const NGConstraintSpace* ConstraintSpace() const { return space_; } -#ifndef NDEBUG +#if DCHECK_IS_ON() String ToString() const; #endif @@ -187,7 +208,7 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { // 2. A fragment containing an out-of-flow positioned-descendant. The // child_offset in this case is the containing fragment's offset. // - // The child_offset is stored as a NGLogicalOffset as the physical offset + // The child_offset is stored as a LogicalOffset as the physical offset // cannot be computed until we know the current fragment's size. // // When returning the positioned-candidates (from @@ -196,10 +217,10 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { // physical size the fragment builder. struct NGOutOfFlowPositionedCandidate { NGOutOfFlowPositionedDescendant descendant; - NGLogicalOffset child_offset; // Logical offset of child's top left vertex. + LogicalOffset child_offset; // Logical offset of child's top left vertex. NGOutOfFlowPositionedCandidate(NGOutOfFlowPositionedDescendant descendant, - NGLogicalOffset child_offset) + LogicalOffset child_offset) : descendant(descendant), child_offset(child_offset) {} }; @@ -210,7 +231,12 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { TextDirection direction) : NGFragmentBuilder(std::move(style), writing_mode, direction), node_(node), - space_(space) {} + space_(space) { + layout_object_ = node.GetLayoutBox(); + } + + void AddChildInternal(scoped_refptr<const NGPhysicalFragment>, + const LogicalOffset&); NGLayoutInputNode node_; const NGConstraintSpace* space_; @@ -227,11 +253,6 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { ChildrenVector children_; - // Logical offsets for the children. Stored as logical offsets as we can't - // convert to physical offsets until layout of all children has been - // determined. - OffsetVector offsets_; - // Only used by the NGBoxFragmentBuilder subclass, but defined here to avoid // a virtual function call. NGBreakTokenVector child_break_tokens_; @@ -239,17 +260,21 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { NGFloatTypes adjoining_floats_ = kFloatTypeNone; + bool is_self_collapsing_ = false; bool is_pushed_by_floats_ = false; - bool is_old_layout_root_ = false; + bool is_legacy_layout_root_ = false; bool has_last_resort_break_ = false; bool has_floating_descendants_ = false; bool has_orthogonal_flow_roots_ = false; - bool has_child_that_depends_on_percentage_block_size_ = false; + bool has_descendant_that_depends_on_percentage_block_size_ = false; bool has_block_fragmentation_ = false; bool may_have_descendant_above_block_start_ = false; }; } // namespace blink +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS( + blink::NGContainerFragmentBuilder::ChildWithOffset) + #endif // NGContainerFragmentBuilder diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc index b1d6eefc023..efdee844b7e 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc @@ -19,11 +19,13 @@ namespace blink { NGFieldsetLayoutAlgorithm::NGFieldsetLayoutAlgorithm( - NGBlockNode node, - const NGConstraintSpace& space, - const NGBreakToken* break_token) - : NGLayoutAlgorithm(node, space, To<NGBlockBreakToken>(break_token)) { - container_builder_.SetIsNewFormattingContext(space.IsNewFormattingContext()); + const NGLayoutAlgorithmParams& params) + : NGLayoutAlgorithm(params), + border_padding_(params.fragment_geometry.border + + params.fragment_geometry.padding) { + container_builder_.SetIsNewFormattingContext( + params.space.IsNewFormattingContext()); + container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); } scoped_refptr<const NGLayoutResult> NGFieldsetLayoutAlgorithm::Layout() { @@ -40,37 +42,38 @@ scoped_refptr<const NGLayoutResult> NGFieldsetLayoutAlgorithm::Layout() { // with the actual fieldset contents. Since scrollbars are handled by the // anonymous child box, and since padding is inside the scrollport, padding // also needs to be handled by the anonymous child. - NGBoxStrut borders = ComputeBorders(ConstraintSpace(), Node()); - NGBoxStrut padding = ComputePadding(ConstraintSpace(), Style()); - NGBoxStrut border_padding = borders + padding; - NGLogicalSize border_box_size = - CalculateBorderBoxSize(ConstraintSpace(), Node(), border_padding); + NGBoxStrut borders = container_builder_.Borders(); + NGBoxStrut padding = container_builder_.Padding(); + LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); const auto writing_mode = ConstraintSpace().GetWritingMode(); - LayoutUnit block_start_padding_edge = borders.block_start; + LayoutUnit block_start_padding_edge = + container_builder_.Borders().block_start; + // TODO(vmpstr): Skip child (including legend) layout for fieldset elements. if (NGBlockNode legend = Node().GetRenderedLegend()) { // Lay out the legend. While the fieldset container normally ignores its // padding, the legend is laid out within what would have been the content // box had the fieldset been a regular block with no weirdness. - NGLogicalSize content_box_size = - ShrinkAvailableSize(border_box_size, border_padding); + LogicalSize content_box_size = + ShrinkAvailableSize(border_box_size, border_padding_); auto legend_space = CreateConstraintSpaceForLegend(legend, content_box_size); auto result = legend.Layout(legend_space, BreakToken()); + const auto& physical_fragment = result->PhysicalFragment(); NGBoxStrut legend_margins = ComputeMarginsFor(legend_space, legend.Style(), ConstraintSpace()); - NGFragment logical_fragment(writing_mode, *result->PhysicalFragment()); // If the margin box of the legend is at least as tall as the fieldset // block-start border width, it will start at the block-start border edge of // the fieldset. As a paint effect, the block-start border will be pushed so // that the center of the border will be flush with the center of the // border-box of the legend. // TODO(mstensho): inline alignment - NGLogicalOffset legend_offset = NGLogicalOffset( - border_padding.inline_start + legend_margins.inline_start, + LogicalOffset legend_offset = LogicalOffset( + border_padding_.inline_start + legend_margins.inline_start, legend_margins.block_start); LayoutUnit legend_margin_box_block_size = - logical_fragment.BlockSize() + legend_margins.BlockSum(); + NGFragment(writing_mode, physical_fragment).BlockSize() + + legend_margins.BlockSum(); LayoutUnit space_left = borders.block_start - legend_margin_box_block_size; if (space_left > LayoutUnit()) { // If the border is the larger one, though, it will stay put at the @@ -87,7 +90,7 @@ scoped_refptr<const NGLayoutResult> NGFieldsetLayoutAlgorithm::Layout() { block_start_padding_edge = legend_margin_box_block_size; } - container_builder_.AddChild(*result, legend_offset); + container_builder_.AddChild(physical_fragment, legend_offset); } NGBoxStrut borders_with_legend = borders; @@ -97,39 +100,47 @@ scoped_refptr<const NGLayoutResult> NGFieldsetLayoutAlgorithm::Layout() { // Proceed with normal fieldset children (excluding the rendered legend). They // all live inside an anonymous child box of the fieldset container. if (auto fieldset_content = Node().GetFieldsetContent()) { - NGLogicalSize adjusted_padding_box_size = + LogicalSize adjusted_padding_box_size = ShrinkAvailableSize(border_box_size, borders_with_legend); auto child_space = CreateConstraintSpaceForFieldsetContent(adjusted_padding_box_size); auto result = fieldset_content.Layout(child_space, BreakToken()); - container_builder_.AddChild(*result, borders_with_legend.StartOffset()); + const auto& physical_fragment = result->PhysicalFragment(); + container_builder_.AddChild(physical_fragment, + borders_with_legend.StartOffset()); - NGFragment logical_fragment(writing_mode, *result->PhysicalFragment()); - intrinsic_block_size += logical_fragment.BlockSize(); + intrinsic_block_size += + NGFragment(writing_mode, physical_fragment).BlockSize(); } else { // There was no anonymous child to provide the padding, so we have to add it // ourselves. intrinsic_block_size += padding.BlockSum(); } + intrinsic_block_size = + ClampIntrinsicBlockSize(Node(), border_padding_, intrinsic_block_size); + // Recompute the block-axis size now that we know our content size. border_box_size.block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_padding, intrinsic_block_size); + ConstraintSpace(), Node(), border_padding_, intrinsic_block_size); // The above computation utility knows nothing about fieldset weirdness. The // legend may eat from the available content box block size. Make room for // that if necessary. - LayoutUnit minimum_border_box_block_size = - borders_with_legend.BlockSum() + padding.BlockSum(); - border_box_size.block_size = - std::max(border_box_size.block_size, minimum_border_box_block_size); + // Note that in size containment, we have to consider sizing as if we have no + // contents, with the conjecture being that legend is part of the contents. + // Thus, only do this adjustment if we do not contain size. + if (!Node().ShouldApplySizeContainment() && + !Node().DisplayLockInducesSizeContainment()) { + LayoutUnit minimum_border_box_block_size = + borders_with_legend.BlockSum() + padding.BlockSum(); + border_box_size.block_size = + std::max(border_box_size.block_size, minimum_border_box_block_size); + } container_builder_.SetIsFieldsetContainer(); - container_builder_.SetInlineSize(border_box_size.inline_size); container_builder_.SetIntrinsicBlockSize(intrinsic_block_size); container_builder_.SetBlockSize(border_box_size.block_size); - container_builder_.SetBorders(borders); - container_builder_.SetPadding(padding); NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders_with_legend, &container_builder_) @@ -142,24 +153,38 @@ base::Optional<MinMaxSize> NGFieldsetLayoutAlgorithm::ComputeMinMaxSize( const MinMaxSizeInput& input) const { MinMaxSize sizes; - // Size-contained elements don't consider their contents for intrinsic sizing. - if (node_.ShouldApplySizeContainment()) - return sizes; + bool apply_size_containment = node_.ShouldApplySizeContainment(); + if (apply_size_containment) { + if (input.size_type == NGMinMaxSizeType::kContentBoxSize) + return sizes; + } else if (node_.DisplayLockInducesSizeContainment()) { + sizes = node_.GetDisplayLockContext().GetLockedContentLogicalWidth(); + if (input.size_type == NGMinMaxSizeType::kContentBoxSize) + return sizes; + apply_size_containment = true; + } - if (NGBlockNode legend = Node().GetRenderedLegend()) { - sizes = ComputeMinAndMaxContentContribution(Style(), legend, input); - sizes += ComputeMinMaxMargins(Style(), legend).InlineSum(); + // Size containment does not consider the legend for sizing. + if (!apply_size_containment) { + if (NGBlockNode legend = Node().GetRenderedLegend()) { + sizes = ComputeMinAndMaxContentContribution(Style(), legend, input); + sizes += ComputeMinMaxMargins(Style(), legend).InlineSum(); + } } + // The fieldset content includes the fieldset padding (and any scrollbars), // while the legend is a regular child and doesn't. We may have a fieldset // without any content or legend, so add the padding here, on the outside. sizes += ComputePadding(ConstraintSpace(), node_.Style()).InlineSum(); - if (NGBlockNode content = Node().GetFieldsetContent()) { - MinMaxSize content_minmax = - ComputeMinAndMaxContentContribution(Style(), content, input); - content_minmax += ComputeMinMaxMargins(Style(), content).InlineSum(); - sizes.Encompass(content_minmax); + // Size containment does not consider the content for sizing. + if (!apply_size_containment) { + if (NGBlockNode content = Node().GetFieldsetContent()) { + MinMaxSize content_minmax = + ComputeMinAndMaxContentContribution(Style(), content, input); + content_minmax += ComputeMinMaxMargins(Style(), content).InlineSum(); + sizes.Encompass(content_minmax); + } } sizes += ComputeBorders(ConstraintSpace(), node_).InlineSum(); @@ -169,13 +194,13 @@ base::Optional<MinMaxSize> NGFieldsetLayoutAlgorithm::ComputeMinMaxSize( const NGConstraintSpace NGFieldsetLayoutAlgorithm::CreateConstraintSpaceForLegend( NGBlockNode legend, - NGLogicalSize available_size) { + LogicalSize available_size) { NGConstraintSpaceBuilder builder( ConstraintSpace(), legend.Style().GetWritingMode(), /* is_new_fc */ true); SetOrthogonalFallbackInlineSizeIfNeeded(Style(), legend, &builder); builder.SetAvailableSize(available_size); - NGLogicalSize percentage_size = + LogicalSize percentage_size = CalculateChildPercentageSize(ConstraintSpace(), Node(), available_size); builder.SetPercentageResolutionSize(percentage_size); builder.SetIsShrinkToFit(legend.Style().LogicalWidth().IsAuto()); @@ -185,14 +210,14 @@ NGFieldsetLayoutAlgorithm::CreateConstraintSpaceForLegend( const NGConstraintSpace NGFieldsetLayoutAlgorithm::CreateConstraintSpaceForFieldsetContent( - NGLogicalSize padding_box_size) { + LogicalSize padding_box_size) { NGConstraintSpaceBuilder builder(ConstraintSpace(), ConstraintSpace().GetWritingMode(), /* is_new_fc */ true); builder.SetAvailableSize(padding_box_size); builder.SetPercentageResolutionSize( ConstraintSpace().PercentageResolutionSize()); - builder.SetIsFixedSizeBlock(padding_box_size.block_size != NGSizeIndefinite); + builder.SetIsFixedSizeBlock(padding_box_size.block_size != kIndefiniteSize); return builder.ToConstraintSpace(); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h index e804a31f9a2..df5a8d3538b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h @@ -7,13 +7,12 @@ #include "third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" namespace blink { class NGBlockBreakToken; -class NGBreakToken; class NGConstraintSpace; class CORE_EXPORT NGFieldsetLayoutAlgorithm @@ -21,9 +20,7 @@ class CORE_EXPORT NGFieldsetLayoutAlgorithm NGBoxFragmentBuilder, NGBlockBreakToken> { public: - NGFieldsetLayoutAlgorithm(NGBlockNode node, - const NGConstraintSpace& space, - const NGBreakToken* break_token = nullptr); + NGFieldsetLayoutAlgorithm(const NGLayoutAlgorithmParams& params); scoped_refptr<const NGLayoutResult> Layout() override; @@ -32,9 +29,11 @@ class CORE_EXPORT NGFieldsetLayoutAlgorithm const NGConstraintSpace CreateConstraintSpaceForLegend( NGBlockNode legend, - NGLogicalSize available_size); + LogicalSize available_size); const NGConstraintSpace CreateConstraintSpaceForFieldsetContent( - NGLogicalSize padding_box_size); + LogicalSize padding_box_size); + + const NGBoxStrut border_padding_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc index bc6964d2162..d1aae7b5fef 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc @@ -6,6 +6,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.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" namespace blink { @@ -30,29 +31,22 @@ class NGFieldsetLayoutAlgorithmTest : public NGBaseLayoutAlgorithmTest { } scoped_refptr<const NGPhysicalBoxFragment> RunBlockLayoutAlgorithm( - const NGConstraintSpace& space, - NGBlockNode node) { - scoped_refptr<const NGLayoutResult> result = - NGBlockLayoutAlgorithm(node, space).Layout(); - - return To<NGPhysicalBoxFragment>(result->PhysicalFragment()); - } - - scoped_refptr<const NGPhysicalBoxFragment> RunBlockLayoutAlgorithm( Element* element) { NGBlockNode container(ToLayoutBox(element->GetLayoutObject())); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite)); - return RunBlockLayoutAlgorithm(space, container); + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); + return NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(container, space); } MinMaxSize RunComputeMinAndMax(NGBlockNode node) { NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - NGLogicalSize(LayoutUnit(), LayoutUnit())); + LogicalSize(LayoutUnit(), LayoutUnit())); + NGFragmentGeometry fragment_geometry = + CalculateInitialMinMaxFragmentGeometry(space, node); - NGFieldsetLayoutAlgorithm algorithm(node, space); + NGFieldsetLayoutAlgorithm algorithm({node, fragment_geometry, space}); MinMaxSizeInput input( /* percentage_resolution_block_size */ (LayoutUnit())); auto min_max = algorithm.ComputeMinMaxSize(input); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc index 2964c16d591..9f7c3d817a2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc @@ -19,17 +19,17 @@ namespace blink { -NGFlexLayoutAlgorithm::NGFlexLayoutAlgorithm(NGBlockNode node, - const NGConstraintSpace& space, - const NGBreakToken* break_token) - : NGLayoutAlgorithm(node, space, To<NGBlockBreakToken>(break_token)), - border_scrollbar_padding_( - CalculateBorderScrollbarPadding(ConstraintSpace(), Node())), - borders_(ComputeBorders(ConstraintSpace(), Node())), - padding_(ComputePadding(ConstraintSpace(), Style())), - border_padding_(borders_ + padding_), +NGFlexLayoutAlgorithm::NGFlexLayoutAlgorithm( + const NGLayoutAlgorithmParams& params) + : NGLayoutAlgorithm(params), + border_padding_(params.fragment_geometry.border + + params.fragment_geometry.padding), + border_scrollbar_padding_(border_padding_ + + params.fragment_geometry.scrollbar), is_column_(Style().IsColumnFlexDirection()) { - container_builder_.SetIsNewFormattingContext(space.IsNewFormattingContext()); + container_builder_.SetIsNewFormattingContext( + params.space.IsNewFormattingContext()); + container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); } bool NGFlexLayoutAlgorithm::MainAxisIsInlineAxis(NGBlockNode child) { @@ -41,7 +41,7 @@ LayoutUnit NGFlexLayoutAlgorithm::MainAxisContentExtent( LayoutUnit sum_hypothetical_main_size) { if (Style().IsColumnFlexDirection()) { return ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_padding_, + ConstraintSpace(), Node(), border_padding_, sum_hypothetical_main_size + (border_padding_).BlockSum()) - border_scrollbar_padding_.BlockSum(); } @@ -84,15 +84,21 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { .SetPercentageResolutionSize(content_box_size_) .ToConstraintSpace(); - NGBoxStrut border_padding_in_child_writing_mode = + NGBoxStrut border_scrollbar_padding_in_child_writing_mode = ComputeBorders(child_space, child) + - ComputePadding(child_space, child_style) + child.GetScrollbarSizes(); + ComputeScrollbars(child_space, child) + + ComputePadding(child_space, child_style); NGPhysicalBoxStrut physical_border_padding( - border_padding_in_child_writing_mode.ConvertToPhysical( + border_scrollbar_padding_in_child_writing_mode.ConvertToPhysical( child_style.GetWritingMode(), child_style.Direction())); LayoutUnit main_axis_border_scrollbar_padding = is_horizontal_flow ? physical_border_padding.HorizontalSum() : physical_border_padding.VerticalSum(); + LayoutUnit main_axis_border_and_padding = + main_axis_border_scrollbar_padding - + (is_horizontal_flow + ? child.GetLayoutBox()->VerticalScrollbarWidth() + : child.GetLayoutBox()->HorizontalScrollbarHeight()); // We want the child's min/max size in its writing mode, not ours. We'll // only ever use it if the child's inline axis is our main axis. @@ -104,7 +110,7 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { scoped_refptr<const NGLayoutResult> layout_result = child.Layout(child_space, nullptr /*break token*/); NGFragment fragment_in_child_writing_mode( - child_style.GetWritingMode(), *layout_result->PhysicalFragment()); + child_style.GetWritingMode(), layout_result->PhysicalFragment()); LayoutUnit flex_base_border_box; const Length& specified_length_in_main_axis = @@ -128,22 +134,26 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { if (MainAxisIsInlineAxis(child)) { flex_base_border_box = ResolveMainInlineLength( - child_space, child_style, border_padding_in_child_writing_mode, + child_space, child_style, + border_scrollbar_padding_in_child_writing_mode, intrinsic_sizes_border_box, length_to_resolve); } else { // Flex container's main axis is in child's block direction. Child's // flex basis is in child's block direction. flex_base_border_box = ResolveMainBlockLength( - child_space, child_style, border_padding_in_child_writing_mode, - length_to_resolve, fragment_in_child_writing_mode.BlockSize(), + child_space, child_style, + border_scrollbar_padding_in_child_writing_mode, length_to_resolve, + fragment_in_child_writing_mode.BlockSize(), LengthResolvePhase::kLayout); } } // Spec calls this "flex base size" // https://www.w3.org/TR/css-flexbox-1/#algo-main-item + // Blink's FlexibleBoxAlgorithm expects it to be content + scrollbar widths, + // but no padding or border. LayoutUnit flex_base_content_size = - flex_base_border_box - main_axis_border_scrollbar_padding; + flex_base_border_box - main_axis_border_and_padding; NGPhysicalBoxStrut physical_child_margins = ComputePhysicalMargins(child_space, child_style); @@ -158,12 +168,14 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { : child.Style().MaxHeight(); if (MainAxisIsInlineAxis(child)) { min_max_sizes_in_main_axis_direction.max_size = ResolveMaxInlineLength( - child_space, child_style, border_padding_in_child_writing_mode, + child_space, child_style, + border_scrollbar_padding_in_child_writing_mode, intrinsic_sizes_border_box, max_property_in_main_axis, LengthResolvePhase::kLayout); } else { min_max_sizes_in_main_axis_direction.max_size = ResolveMaxBlockLength( - child_space, child_style, border_padding_in_child_writing_mode, + child_space, child_style, + border_scrollbar_padding_in_child_writing_mode, max_property_in_main_axis, fragment_in_child_writing_mode.BlockSize(), LengthResolvePhase::kLayout); } @@ -192,7 +204,8 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { // resolved specified_length_in_main_axis in the flex basis // calculation. Reuse that if possible. specified_size_suggestion = ResolveMainInlineLength( - child_space, child_style, border_padding_in_child_writing_mode, + child_space, child_style, + border_scrollbar_padding_in_child_writing_mode, intrinsic_sizes_border_box, specified_length_in_main_axis); } } else if (!specified_length_in_main_axis.IsAuto() && @@ -200,10 +213,11 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { specified_length_in_main_axis, LengthResolvePhase::kLayout)) { specified_size_suggestion = ResolveMainBlockLength( - child_space, child_style, border_padding_in_child_writing_mode, + child_space, child_style, + border_scrollbar_padding_in_child_writing_mode, specified_length_in_main_axis, layout_result->IntrinsicBlockSize(), LengthResolvePhase::kLayout); - DCHECK_NE(specified_size_suggestion, NGSizeIndefinite); + DCHECK_NE(specified_size_suggestion, kIndefiniteSize); } // Spec says to clamp specified_size_suggestion by max size but because // content_size_suggestion already is, and we take the min of those @@ -217,17 +231,18 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { } } else if (MainAxisIsInlineAxis(child)) { min_max_sizes_in_main_axis_direction.min_size = - ResolveMinInlineLength( - child_space, child_style, border_padding_in_child_writing_mode, - intrinsic_sizes_border_box, min, LengthResolvePhase::kLayout) - + ResolveMinInlineLength(child_space, child_style, + border_scrollbar_padding_in_child_writing_mode, + intrinsic_sizes_border_box, min, + LengthResolvePhase::kLayout) - main_axis_border_scrollbar_padding; // TODO(dgrogan): No tests changed status as result of subtracting // main_axis_border_scrollbar_padding. It may be untested. } else { min_max_sizes_in_main_axis_direction.min_size = ResolveMinBlockLength(child_space, child_style, - border_padding_in_child_writing_mode, min, - fragment_in_child_writing_mode.BlockSize(), + border_scrollbar_padding_in_child_writing_mode, + min, fragment_in_child_writing_mode.BlockSize(), LengthResolvePhase::kLayout) - main_axis_border_scrollbar_padding; // TODO(dgrogan): Same as above WRT subtracting @@ -237,14 +252,13 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { algorithm_ ->emplace_back(child.GetLayoutBox(), flex_base_content_size, min_max_sizes_in_main_axis_direction, - main_axis_border_scrollbar_padding, main_axis_margin) + main_axis_border_and_padding, main_axis_margin) .ng_input_node = child; } } scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { - border_box_size_ = - CalculateBorderBoxSize(ConstraintSpace(), Node(), border_padding_); + border_box_size_ = container_builder_.InitialBorderBoxSize(); content_box_size_ = ShrinkAvailableSize(border_box_size_, border_scrollbar_padding_); @@ -281,17 +295,15 @@ scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { SetOrthogonalFallbackInlineSizeIfNeeded(Style(), flex_item.ng_input_node, &space_builder); - NGLogicalSize available_size; + LogicalSize available_size; if (is_column_) { available_size.inline_size = content_box_size_.inline_size; - available_size.block_size = - flex_item.flexed_content_size + - flex_item.main_axis_border_scrollbar_padding; + available_size.block_size = flex_item.flexed_content_size + + flex_item.main_axis_border_and_padding; space_builder.SetIsFixedSizeBlock(true); } else { - available_size.inline_size = - flex_item.flexed_content_size + - flex_item.main_axis_border_scrollbar_padding; + available_size.inline_size = flex_item.flexed_content_size + + flex_item.main_axis_border_and_padding; available_size.block_size = content_box_size_.block_size; space_builder.SetIsFixedSizeInline(true); } @@ -302,8 +314,8 @@ scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { flex_item.ng_input_node.Layout(child_space, nullptr /*break token*/); flex_item.cross_axis_size = is_horizontal_flow - ? flex_item.layout_result->PhysicalFragment()->Size().height - : flex_item.layout_result->PhysicalFragment()->Size().width; + ? flex_item.layout_result->PhysicalFragment().Size().height + : flex_item.layout_result->PhysicalFragment().Size().width; } // cross_axis_offset is updated in each iteration of the loop, for passing // in to the next iteration. @@ -317,21 +329,19 @@ scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { LayoutUnit intrinsic_block_size = intrinsic_block_content_size + border_scrollbar_padding_.BlockSum(); LayoutUnit block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_padding_, intrinsic_block_size); + ConstraintSpace(), Node(), border_padding_, intrinsic_block_size); container_builder_.SetBlockSize(block_size); - container_builder_.SetInlineSize(border_box_size_.inline_size); - container_builder_.SetBorders(borders_); - container_builder_.SetPadding(padding_); container_builder_.SetIntrinsicBlockSize( algorithm_->IntrinsicContentBlockSize() + border_scrollbar_padding_.BlockSum()); GiveLinesAndItemsFinalPositionAndSize(); - NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), - borders_ + Node().GetScrollbarSizes(), - &container_builder_) + NGOutOfFlowLayoutPart( + Node(), ConstraintSpace(), + container_builder_.Borders() + container_builder_.Scrollbar(), + &container_builder_) .Run(); return container_builder_.ToBoxFragment(); @@ -366,12 +376,11 @@ void NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() { SetOrthogonalFallbackInlineSizeIfNeeded( Style(), flex_item.ng_input_node, &space_builder); - NGLogicalSize available_size( - flex_item.flexed_content_size + - flex_item.main_axis_border_scrollbar_padding, - flex_item.cross_axis_size); + LogicalSize available_size(flex_item.flexed_content_size + + flex_item.main_axis_border_and_padding, + flex_item.cross_axis_size); if (is_column_) - available_size.Flip(); + available_size.Transpose(); space_builder.SetAvailableSize(available_size); space_builder.SetPercentageResolutionSize(content_box_size_); space_builder.SetIsFixedSizeInline(true); @@ -388,7 +397,7 @@ void NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() { // non-horizontal flexboxes, like // LayoutFlexibleBox::SetFlowAwareLocationForChild does? container_builder_.AddChild( - *flex_item.layout_result, + flex_item.layout_result->PhysicalFragment(), {flex_item.desired_location.X(), flex_item.desired_location.Y()}); } } @@ -396,18 +405,15 @@ void NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() { base::Optional<MinMaxSize> NGFlexLayoutAlgorithm::ComputeMinMaxSize( const MinMaxSizeInput& input) const { - MinMaxSize sizes; - if (Node().ShouldApplySizeContainment()) { - // TODO(dgrogan): When this code was written it didn't make any more tests - // pass, so it may be wrong or untested. - if (input.size_type == NGMinMaxSizeType::kBorderBoxSize) - sizes = border_scrollbar_padding_.InlineSum(); + base::Optional<MinMaxSize> sizes = CalculateMinMaxSizesIgnoringChildren( + Node(), border_scrollbar_padding_, input.size_type); + if (sizes) return sizes; - } + sizes.emplace(); LayoutUnit child_percentage_resolution_block_size = CalculateChildPercentageBlockSizeForMinMax( - ConstraintSpace(), Node(), borders_ + padding_, + ConstraintSpace(), Node(), border_padding_, input.percentage_resolution_block_size); // Use default MinMaxSizeInput: @@ -429,24 +435,26 @@ base::Optional<MinMaxSize> NGFlexLayoutAlgorithm::ComputeMinMaxSize( NGBoxStrut child_margins = ComputeMinMaxMargins(Style(), child); child_min_max_sizes += child_margins.InlineSum(); if (is_column_) { - sizes.min_size = std::max(sizes.min_size, child_min_max_sizes.min_size); - sizes.max_size = std::max(sizes.max_size, child_min_max_sizes.max_size); + sizes->min_size = std::max(sizes->min_size, child_min_max_sizes.min_size); + sizes->max_size = std::max(sizes->max_size, child_min_max_sizes.max_size); } else { - sizes.max_size += child_min_max_sizes.max_size; - if (IsMultiline()) - sizes.min_size = std::max(sizes.min_size, child_min_max_sizes.min_size); - else - sizes.min_size += child_min_max_sizes.min_size; + sizes->max_size += child_min_max_sizes.max_size; + if (IsMultiline()) { + sizes->min_size = + std::max(sizes->min_size, child_min_max_sizes.min_size); + } else { + sizes->min_size += child_min_max_sizes.min_size; + } } } - sizes.max_size = std::max(sizes.max_size, sizes.min_size); + sizes->max_size = std::max(sizes->max_size, sizes->min_size); // Due to negative margins, it is possible that we calculated a negative // intrinsic width. Make sure that we never return a negative width. - sizes.Encompass(LayoutUnit()); + sizes->Encompass(LayoutUnit()); if (input.size_type == NGMinMaxSizeType::kBorderBoxSize) - sizes += border_scrollbar_padding_.InlineSum(); + *sizes += border_scrollbar_padding_.InlineSum(); return sizes; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h index a85d0418c16..f33c5e0d40d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h @@ -14,17 +14,13 @@ namespace blink { class NGBlockNode; class NGBlockBreakToken; -class NGBreakToken; -class NGConstraintSpace; class CORE_EXPORT NGFlexLayoutAlgorithm : public NGLayoutAlgorithm<NGBlockNode, NGBoxFragmentBuilder, NGBlockBreakToken> { public: - NGFlexLayoutAlgorithm(NGBlockNode, - const NGConstraintSpace&, - const NGBreakToken*); + NGFlexLayoutAlgorithm(const NGLayoutAlgorithmParams& params); scoped_refptr<const NGLayoutResult> Layout() override; @@ -45,13 +41,11 @@ class CORE_EXPORT NGFlexLayoutAlgorithm // not reimplement. bool IsMultiline() const; - const NGBoxStrut border_scrollbar_padding_; - const NGBoxStrut borders_; - const NGBoxStrut padding_; const NGBoxStrut border_padding_; + const NGBoxStrut border_scrollbar_padding_; const bool is_column_; - NGLogicalSize border_box_size_; - NGLogicalSize content_box_size_; + LogicalSize border_box_size_; + LogicalSize content_box_size_; // This is populated at the top of Layout(), so isn't available in // ComputeMinMaxSize() or anything it calls. base::Optional<FlexLayoutAlgorithm> algorithm_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc index e8c6ba8ebd0..e3127d0e7a9 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc @@ -38,7 +38,7 @@ NGBfcOffset AdjustToTopEdgeAlignmentRule( } NGLayoutOpportunity FindLayoutOpportunityForFloat( - const NGLogicalSize& float_available_size, + const LogicalSize& float_available_size, const NGBfcOffset& origin_bfc_offset, const NGExclusionSpace& exclusion_space, const NGUnpositionedFloat& unpositioned_float, @@ -52,8 +52,8 @@ NGLayoutOpportunity FindLayoutOpportunityForFloat( AdjustToClearance(clearance_offset, &adjusted_origin_point); - NGLogicalSize float_size(inline_size + fragment_margins.InlineSum(), - LayoutUnit()); + LogicalSize float_size(inline_size + fragment_margins.InlineSum(), + LayoutUnit()); return exclusion_space.FindLayoutOpportunity( adjusted_origin_point, float_available_size.inline_size, float_size); } @@ -61,9 +61,9 @@ NGLayoutOpportunity FindLayoutOpportunityForFloat( // Creates a constraint space for an unpositioned float. origin_block_offset // should only be set when we want to fragmentation to occur. NGConstraintSpace CreateConstraintSpaceForFloat( - const NGLogicalSize& float_available_size, - const NGLogicalSize& float_percentage_size, - const NGLogicalSize& float_replaced_percentage_size, + const LogicalSize& float_available_size, + const LogicalSize& float_percentage_size, + const LogicalSize& float_replaced_percentage_size, const NGUnpositionedFloat& unpositioned_float, const NGConstraintSpace& parent_space, const ComputedStyle& parent_style, @@ -96,9 +96,9 @@ NGConstraintSpace CreateConstraintSpaceForFloat( } std::unique_ptr<NGExclusionShapeData> CreateExclusionShapeData( - const NGLogicalSize& float_available_size, - const NGLogicalSize& float_percentage_size, - const NGLogicalSize& float_replaced_percentage_size, + const LogicalSize& float_available_size, + const LogicalSize& float_percentage_size, + const LogicalSize& float_replaced_percentage_size, const NGBoxStrut& margins, const NGUnpositionedFloat& unpositioned_float, const NGConstraintSpace& parent_space, @@ -144,9 +144,9 @@ std::unique_ptr<NGExclusionShapeData> CreateExclusionShapeData( // Creates an exclusion from the fragment that will be placed in the provided // layout opportunity. scoped_refptr<const NGExclusion> CreateExclusion( - const NGLogicalSize& float_available_size, - const NGLogicalSize& float_percentage_size, - const NGLogicalSize& float_replaced_percentage_size, + const LogicalSize& float_available_size, + const LogicalSize& float_percentage_size, + const LogicalSize& float_replaced_percentage_size, const NGFragment& fragment, const NGBfcOffset& float_margin_bfc_offset, const NGBoxStrut& margins, @@ -176,9 +176,9 @@ scoped_refptr<const NGExclusion> CreateExclusion( // Performs layout on a float, without fragmentation, and stores the result on // the NGUnpositionedFloat data-structure. void LayoutFloatWithoutFragmentation( - const NGLogicalSize& float_available_size, - const NGLogicalSize& float_percentage_size, - const NGLogicalSize& float_replaced_percentage_size, + const LogicalSize& float_available_size, + const LogicalSize& float_percentage_size, + const LogicalSize& float_replaced_percentage_size, const NGConstraintSpace& parent_space, const ComputedStyle& parent_style, NGUnpositionedFloat* unpositioned_float) { @@ -211,19 +211,18 @@ LayoutUnit ComputeMarginBoxInlineSizeForUnpositionedFloat( parent_style, unpositioned_float); DCHECK(unpositioned_float->layout_result); - const auto* fragment = unpositioned_float->layout_result->PhysicalFragment(); - DCHECK(fragment); - DCHECK(!fragment->BreakToken() || fragment->BreakToken()->IsFinished()); + const auto& fragment = unpositioned_float->layout_result->PhysicalFragment(); + DCHECK(!fragment.BreakToken() || fragment.BreakToken()->IsFinished()); - return (NGFragment(parent_space.GetWritingMode(), *fragment).InlineSize() + + return (NGFragment(parent_space.GetWritingMode(), fragment).InlineSize() + unpositioned_float->margins.InlineSum()) .ClampNegativeToZero(); } NGPositionedFloat PositionFloat( - const NGLogicalSize& float_available_size, - const NGLogicalSize& float_percentage_size, - const NGLogicalSize& float_replaced_percentage_size, + const LogicalSize& float_available_size, + const LogicalSize& float_percentage_size, + const LogicalSize& float_replaced_percentage_size, const NGBfcOffset& origin_bfc_offset, NGUnpositionedFloat* unpositioned_float, const NGConstraintSpace& parent_space, @@ -265,15 +264,14 @@ NGPositionedFloat PositionFloat( unpositioned_float->token.get())) fragment_margins.block_start = LayoutUnit(); if (const NGBreakToken* break_token = - layout_result->PhysicalFragment()->BreakToken()) { + layout_result->PhysicalFragment().BreakToken()) { if (!break_token->IsFinished()) fragment_margins.block_end = LayoutUnit(); } } - DCHECK(layout_result->PhysicalFragment()); NGFragment float_fragment(parent_space.GetWritingMode(), - *layout_result->PhysicalFragment()); + layout_result->PhysicalFragment()); // Find a layout opportunity that will fit our float. NGLayoutOpportunity opportunity = FindLayoutOpportunityForFloat( diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.h index 554f9791270..1b6d9cb3eb2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.h @@ -17,17 +17,19 @@ class ComputedStyle; class NGConstraintSpace; class NGExclusionSpace; struct NGBfcOffset; -struct NGLogicalSize; +struct LogicalSize; struct NGPositionedFloat; struct NGUnpositionedFloat; typedef Vector<NGPositionedFloat, 8> NGPositionedFloatVector; +// TODO(ikilpatrick): Rename this to NGAdjoiningObjectTypes. enum NGFloatTypeValue { - kFloatTypeNone = 0b00, - kFloatTypeLeft = 0b01, - kFloatTypeRight = 0b10, - kFloatTypeBoth = 0b11 + kFloatTypeNone = 0b000, + kFloatTypeLeft = 0b001, + kFloatTypeRight = 0b010, + kFloatTypeBoth = 0b011, + kAdjoiningInlineOutOfFlow = 0b100 }; typedef int NGFloatTypes; @@ -41,9 +43,9 @@ LayoutUnit ComputeMarginBoxInlineSizeForUnpositionedFloat( // Positions {@code unpositioned_float} into {@code new_parent_space}. // @returns A positioned float. CORE_EXPORT NGPositionedFloat -PositionFloat(const NGLogicalSize& float_available_size, - const NGLogicalSize& float_percentage_size, - const NGLogicalSize& float_replaced_percentage_size, +PositionFloat(const LogicalSize& float_available_size, + const LogicalSize& float_percentage_size, + const LogicalSize& float_replaced_percentage_size, const NGBfcOffset& origin_bfc_offset, NGUnpositionedFloat*, const NGConstraintSpace& parent_space, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment.h index e02d39a23e7..cd5ac2aac6d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment.h @@ -13,8 +13,7 @@ namespace blink { -struct NGBorderEdges; -struct NGLogicalSize; +struct LogicalSize; class CORE_EXPORT NGFragment { STACK_ALLOCATED(); @@ -40,16 +39,11 @@ class CORE_EXPORT NGFragment { ? physical_fragment_.Size().height : physical_fragment_.Size().width; } - NGLogicalSize Size() const { + LogicalSize Size() const { return physical_fragment_.Size().ConvertToLogical( static_cast<WritingMode>(writing_mode_)); } - NGBorderEdges BorderEdges() const { - return NGBorderEdges::FromPhysical(physical_fragment_.BorderEdges(), - GetWritingMode()); - } - NGPhysicalFragment::NGFragmentType Type() const { return physical_fragment_.Type(); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h index 09a488bdd40..ec3b02fc22d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h @@ -7,7 +7,7 @@ #include "base/memory/scoped_refptr.h" #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/ng_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_style_variant.h" #include "third_party/blink/renderer/core/style/computed_style.h" @@ -20,7 +20,7 @@ namespace blink { class LayoutObject; class CORE_EXPORT NGFragmentBuilder { - STACK_ALLOCATED(); + DISALLOW_NEW(); public: const ComputedStyle& Style() const { @@ -44,12 +44,7 @@ class CORE_EXPORT NGFragmentBuilder { LayoutUnit InlineSize() const { return size_.inline_size; } LayoutUnit BlockSize() const { return size_.block_size; } - const NGLogicalSize& Size() const { return size_; } - NGFragmentBuilder& SetInlineSize(LayoutUnit inline_size) { - DCHECK_GE(inline_size, LayoutUnit()); - size_.inline_size = inline_size; - return *this; - } + const LogicalSize& Size() const { return size_; } void SetBlockSize(LayoutUnit block_size) { size_.block_size = block_size; } const LayoutObject* GetLayoutObject() const { return layout_object_; } @@ -72,7 +67,7 @@ class CORE_EXPORT NGFragmentBuilder { WritingMode writing_mode_; TextDirection direction_; NGStyleVariant style_variant_; - NGLogicalSize size_; + LogicalSize size_; LayoutObject* layout_object_ = nullptr; scoped_refptr<NGBreakToken> break_token_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc index e61ae6a5e5e..9c556e3468b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc @@ -17,12 +17,13 @@ LayoutUnit PreviouslyUsedBlockSpace(const NGConstraintSpace& constraint_space, const NGPhysicalFragment& fragment) { if (!fragment.IsBox()) return LayoutUnit(); - const auto* break_token = To<NGBlockBreakToken>(fragment.BreakToken()); + const NGPhysicalBoxFragment& box_fragment = + To<NGPhysicalBoxFragment>(fragment); + const auto* break_token = To<NGBlockBreakToken>(box_fragment.BreakToken()); if (!break_token) return LayoutUnit(); NGBoxFragment logical_fragment(constraint_space.GetWritingMode(), - constraint_space.Direction(), - To<NGPhysicalBoxFragment>(fragment)); + constraint_space.Direction(), box_fragment); return break_token->UsedBlockSize() - logical_fragment.BlockSize(); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h index 6231e4e5572..31ead905d04 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h @@ -5,9 +5,9 @@ #ifndef NGFragmentationUtils_h #define NGFragmentationUtils_h -#include "third_party/blink/renderer/core/layout/ng/ng_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h" -#include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h" #include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h" @@ -34,7 +34,10 @@ inline bool IsFirstFragment(const NGConstraintSpace& constraint_space, // Return true if the specified fragment is the final fragment of some node. inline bool IsLastFragment(const NGPhysicalFragment& fragment) { - const auto* break_token = fragment.BreakToken(); + if (!fragment.IsContainer()) + return false; + const auto* break_token = + To<NGPhysicalContainerFragment>(fragment).BreakToken(); return !break_token || break_token->IsFinished(); } @@ -63,6 +66,11 @@ bool ShouldIgnoreBlockStartMargin(const NGConstraintSpace&, NGLayoutInputNode, const NGBreakToken*); +// Return true if we're resuming layout after a previous break. +inline bool IsResumingLayout(const NGBlockBreakToken* token) { + return token && !token->IsBreakBefore(); +} + } // namespace blink #endif // NGFragmentationUtils_h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_inline_layout_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_inline_layout_test.cc index 17487fa66c7..23072c83512 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_inline_layout_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_inline_layout_test.cc @@ -8,6 +8,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" #include "third_party/blink/renderer/core/testing/sim/sim_compositor.h" #include "third_party/blink/renderer/core/testing/sim/sim_request.h" #include "third_party/blink/renderer/core/testing/sim/sim_test.h" @@ -21,12 +22,11 @@ namespace blink { class NGInlineLayoutTest : public SimTest { public: NGConstraintSpace ConstraintSpaceForElement(LayoutBlockFlow* block_flow) { - return NGConstraintSpaceBuilder( - block_flow->Style()->GetWritingMode(), - block_flow->Style()->GetWritingMode(), - /* is_new_fc */ false) - .SetAvailableSize(NGLogicalSize(LayoutUnit(), LayoutUnit())) - .SetPercentageResolutionSize(NGLogicalSize(LayoutUnit(), LayoutUnit())) + return NGConstraintSpaceBuilder(block_flow->Style()->GetWritingMode(), + block_flow->Style()->GetWritingMode(), + /* is_new_fc */ false) + .SetAvailableSize(LogicalSize(LayoutUnit(), LayoutUnit())) + .SetPercentageResolutionSize(LogicalSize(LayoutUnit(), LayoutUnit())) .SetTextDirection(block_flow->Style()->Direction()) .ToConstraintSpace(); } @@ -48,8 +48,11 @@ TEST_F(NGInlineLayoutTest, BlockWithSingleTextNode) { NGConstraintSpace constraint_space = ConstraintSpaceForElement(block_flow); NGBlockNode node(block_flow); + NGFragmentGeometry fragment_geometry = + CalculateInitialFragmentGeometry(constraint_space, node); scoped_refptr<const NGLayoutResult> result = - NGBlockLayoutAlgorithm(node, constraint_space).Layout(); + NGBlockLayoutAlgorithm({node, fragment_geometry, constraint_space}) + .Layout(); EXPECT_TRUE(result); String expected_text("Hello World!"); @@ -73,8 +76,11 @@ TEST_F(NGInlineLayoutTest, BlockWithTextAndAtomicInline) { NGConstraintSpace constraint_space = ConstraintSpaceForElement(block_flow); NGBlockNode node(block_flow); + NGFragmentGeometry fragment_geometry = + CalculateInitialFragmentGeometry(constraint_space, node); scoped_refptr<const NGLayoutResult> result = - NGBlockLayoutAlgorithm(node, constraint_space).Layout(); + NGBlockLayoutAlgorithm({node, fragment_geometry, constraint_space}) + .Layout(); EXPECT_TRUE(result); StringBuilder expected_text; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h index aa4aa8ba2a7..003277b57ef 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h @@ -8,6 +8,7 @@ #include "base/optional.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/min_max_size.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/platform/wtf/allocator.h" @@ -37,12 +38,33 @@ class NGLayoutAlgorithmOperations { } }; +// Parameters to pass when creating a layout algorithm for a block node. +struct NGLayoutAlgorithmParams { + STACK_ALLOCATED(); + + public: + NGLayoutAlgorithmParams(NGBlockNode node, + const NGFragmentGeometry& fragment_geometry, + const NGConstraintSpace& space, + const NGBlockBreakToken* break_token = nullptr) + : node(node), + fragment_geometry(fragment_geometry), + space(space), + break_token(break_token) {} + + NGBlockNode node; + const NGFragmentGeometry& fragment_geometry; + const NGConstraintSpace& space; + const NGBlockBreakToken* break_token; +}; + // Base class for all LayoutNG algorithms. template <typename NGInputNodeType, typename NGBoxFragmentBuilderType, typename NGBreakTokenType> class CORE_EXPORT NGLayoutAlgorithm : public NGLayoutAlgorithmOperations { - STACK_ALLOCATED(); + USING_FAST_MALLOC(NGLayoutAlgorithm); + public: NGLayoutAlgorithm(NGInputNodeType node, scoped_refptr<const ComputedStyle> style, @@ -57,14 +79,12 @@ class CORE_EXPORT NGLayoutAlgorithm : public NGLayoutAlgorithmOperations { space.GetWritingMode(), direction) {} - NGLayoutAlgorithm(NGInputNodeType node, - const NGConstraintSpace& space, - const NGBreakTokenType* break_token) - : NGLayoutAlgorithm(node, - &node.Style(), - space, - space.Direction(), - break_token) {} + NGLayoutAlgorithm(const NGLayoutAlgorithmParams& params) + : NGLayoutAlgorithm(params.node, + ¶ms.node.Style(), + params.space, + params.space.Direction(), + params.break_token) {} virtual ~NGLayoutAlgorithm() = default; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc index f44232ed371..c6cc9856596 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc @@ -4,12 +4,12 @@ #include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h" #include "third_party/blink/renderer/core/layout/layout_replaced.h" #include "third_party/blink/renderer/core/layout/layout_table_cell.h" #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/min_max_size.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" @@ -19,7 +19,7 @@ namespace blink { namespace { -#ifndef NDEBUG +#if DCHECK_IS_ON() void AppendNodeToString(NGLayoutInputNode node, StringBuilder* string_builder, unsigned indent = 2) { @@ -62,15 +62,6 @@ void AppendNodeToString(NGLayoutInputNode node, } // namespace -scoped_refptr<const NGLayoutResult> NGLayoutInputNode::Layout( - const NGConstraintSpace& space, - const NGBreakToken* break_token, - NGInlineChildLayoutContext* context) { - auto* inline_node = DynamicTo<NGInlineNode>(this); - return inline_node ? inline_node->Layout(space, break_token, context) - : To<NGBlockNode>(*this).Layout(space, break_token); -} - MinMaxSize NGLayoutInputNode::ComputeMinMaxSize( WritingMode writing_mode, const MinMaxSizeInput& input, @@ -83,12 +74,20 @@ MinMaxSize NGLayoutInputNode::ComputeMinMaxSize( void NGLayoutInputNode::IntrinsicSize( base::Optional<LayoutUnit>* computed_inline_size, base::Optional<LayoutUnit>* computed_block_size, - NGLogicalSize* aspect_ratio) const { + LogicalSize* aspect_ratio) const { DCHECK(IsReplaced()); if (ShouldApplySizeContainment()) { *computed_inline_size = LayoutUnit(); *computed_block_size = LayoutUnit(); - *aspect_ratio = NGLogicalSize(LayoutUnit(), LayoutUnit()); + *aspect_ratio = LogicalSize(LayoutUnit(), LayoutUnit()); + return; + } + if (DisplayLockInducesSizeContainment()) { + *computed_inline_size = + GetDisplayLockContext().GetLockedContentLogicalWidth(); + *computed_block_size = + GetDisplayLockContext().GetLockedContentLogicalHeight(); + *aspect_ratio = LogicalSize(**computed_inline_size, **computed_block_size); return; } IntrinsicSizingInfo legacy_sizing_info; @@ -99,8 +98,8 @@ void NGLayoutInputNode::IntrinsicSize( if (legacy_sizing_info.has_height) *computed_block_size = LayoutUnit(legacy_sizing_info.size.Height()); *aspect_ratio = - NGLogicalSize(LayoutUnit(legacy_sizing_info.aspect_ratio.Width()), - LayoutUnit(legacy_sizing_info.aspect_ratio.Height())); + LogicalSize(LayoutUnit(legacy_sizing_info.aspect_ratio.Width()), + LayoutUnit(legacy_sizing_info.aspect_ratio.Height())); } LayoutUnit NGLayoutInputNode::IntrinsicPaddingBlockStart() const { @@ -119,11 +118,10 @@ NGLayoutInputNode NGLayoutInputNode::NextSibling() { : To<NGBlockNode>(*this).NextSibling(); } -NGPhysicalSize NGLayoutInputNode::InitialContainingBlockSize() const { +PhysicalSize NGLayoutInputNode::InitialContainingBlockSize() const { IntSize icb_size = GetDocument().GetLayoutView()->GetLayoutSize(kExcludeScrollbars); - return NGPhysicalSize{LayoutUnit(icb_size.Width()), - LayoutUnit(icb_size.Height())}; + return PhysicalSize(icb_size); } const NGPaintFragment* NGLayoutInputNode::PaintFragment() const { @@ -136,7 +134,7 @@ String NGLayoutInputNode::ToString() const { : To<NGBlockNode>(*this).ToString(); } -#ifndef NDEBUG +#if DCHECK_IS_ON() void NGLayoutInputNode::ShowNodeTree() const { StringBuilder string_builder; string_builder.Append(".:: LayoutNG Node Tree ::.\n"); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h index c65f36812b8..0c1c0d40168 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h @@ -7,8 +7,9 @@ #include "base/optional.h" #include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/display_lock/display_lock_context.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/layout_box.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" #include "third_party/blink/renderer/core/layout/ng/layout_box_utils.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h" @@ -17,17 +18,15 @@ namespace blink { class ComputedStyle; +class DisplayLockContext; class Document; class LayoutObject; class LayoutBox; -class NGBreakToken; class NGConstraintSpace; -class NGInlineChildLayoutContext; -class NGLayoutResult; class NGPaintFragment; struct MinMaxSize; -struct NGLogicalSize; -struct NGPhysicalSize; +struct LogicalSize; +struct PhysicalSize; enum class NGMinMaxSizeType { kContentBoxSize, kBorderBoxSize }; @@ -82,6 +81,7 @@ class CORE_EXPORT NGLayoutInputNode { bool IsInline() const { return type_ == kInline; } bool IsBlock() const { return type_ == kBlock; } + bool IsBlockFlow() const { return IsBlock() && box_->IsLayoutBlockFlow(); } bool IsColumnSpanAll() const { return IsBlock() && box_->IsColumnSpanAll(); } bool IsFloating() const { return IsBlock() && Style().IsFloating(); } bool IsOutOfFlowPositioned() const { @@ -97,6 +97,9 @@ class CORE_EXPORT NGLayoutInputNode { bool IsBody() const { return IsBlock() && box_->IsBody(); } bool IsDocumentElement() const { return box_->IsDocumentElement(); } bool IsFlexItem() const { return IsBlock() && box_->IsFlexItemIncludingNG(); } + bool IsFlexibleBox() const { + return IsBlock() && box_->IsFlexibleBoxIncludingNG(); + } bool ShouldBeConsideredAsReplaced() const { return box_->ShouldBeConsideredAsReplaced(); } @@ -153,11 +156,6 @@ class CORE_EXPORT NGLayoutInputNode { return false; } - // Performs layout on this input node, will return the layout result. - scoped_refptr<const NGLayoutResult> Layout(const NGConstraintSpace&, - const NGBreakToken*, - NGInlineChildLayoutContext*); - // Returns border box. MinMaxSize ComputeMinMaxSize(WritingMode, const MinMaxSizeInput&, @@ -168,7 +166,7 @@ class CORE_EXPORT NGLayoutInputNode { // Corresponds to Legacy's LayoutReplaced::IntrinsicSizingInfo. void IntrinsicSize(base::Optional<LayoutUnit>* computed_inline_size, base::Optional<LayoutUnit>* computed_block_size, - NGLogicalSize* aspect_ratio) const; + LogicalSize* aspect_ratio) const; LayoutUnit IntrinsicPaddingBlockStart() const; LayoutUnit IntrinsicPaddingBlockEnd() const; @@ -178,7 +176,7 @@ class CORE_EXPORT NGLayoutInputNode { Document& GetDocument() const { return box_->GetDocument(); } - NGPhysicalSize InitialContainingBlockSize() const; + PhysicalSize InitialContainingBlockSize() const; // Returns the LayoutObject which is associated with this node. LayoutBox* GetLayoutBox() const { return box_; } @@ -189,6 +187,19 @@ class CORE_EXPORT NGLayoutInputNode { return box_->ShouldApplySizeContainment(); } + // Display locking functionality. + const DisplayLockContext& GetDisplayLockContext() const { + DCHECK(box_->GetDisplayLockContext()); + return *box_->GetDisplayLockContext(); + } + bool DisplayLockInducesSizeContainment() const { + return box_->DisplayLockInducesSizeContainment(); + } + bool LayoutBlockedByDisplayLock( + DisplayLockContext::LifecycleTarget target) const { + return box_->LayoutBlockedByDisplayLock(target); + } + // Returns the first NGPaintFragment for this node. When block fragmentation // occurs, there will be multiple NGPaintFragment for a node. const NGPaintFragment* PaintFragment() const; @@ -205,7 +216,7 @@ class CORE_EXPORT NGLayoutInputNode { return !(*this == other); } -#ifndef NDEBUG +#if DCHECK_IS_ON() void ShowNodeTree() const; #endif diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc index b6c86d4b949..f2d5ddb46c2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc @@ -10,37 +10,40 @@ #include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h" namespace blink { NGLayoutResult::NGLayoutResult( - scoped_refptr<const NGPhysicalFragment> physical_fragment, + scoped_refptr<const NGPhysicalContainerFragment> physical_fragment, NGBoxFragmentBuilder* builder) - : NGLayoutResult(builder, /* cache_space */ true) { + : NGLayoutResult(std::move(physical_fragment), + builder, + /* cache_space */ true) { + is_initial_block_size_indefinite_ = + builder->is_initial_block_size_indefinite_; intrinsic_block_size_ = builder->intrinsic_block_size_; minimal_space_shortage_ = builder->minimal_space_shortage_; initial_break_before_ = builder->initial_break_before_; final_break_after_ = builder->previous_break_after_; has_forced_break_ = builder->has_forced_break_; - DCHECK(physical_fragment) << "Use the other constructor for aborting layout"; - physical_fragment_ = std::move(physical_fragment); - oof_positioned_descendants_ = std::move(builder->oof_positioned_descendants_); } NGLayoutResult::NGLayoutResult( - scoped_refptr<const NGPhysicalFragment> physical_fragment, + scoped_refptr<const NGPhysicalContainerFragment> physical_fragment, NGLineBoxFragmentBuilder* builder) - : NGLayoutResult(builder, /* cache_space */ false) { - physical_fragment_ = std::move(physical_fragment); - oof_positioned_descendants_ = std::move(builder->oof_positioned_descendants_); -} + : NGLayoutResult(std::move(physical_fragment), + builder, + /* cache_space */ false) {} NGLayoutResult::NGLayoutResult(NGLayoutResultStatus status, NGBoxFragmentBuilder* builder) - : NGLayoutResult(builder, /* cache_space */ false) { + : NGLayoutResult(/* physical_fragment */ nullptr, + builder, + /* cache_space */ false) { adjoining_floats_ = kFloatTypeNone; - depends_on_percentage_block_size_ = false; + has_descendant_that_depends_on_percentage_block_size_ = false; status_ = status; DCHECK_NE(status, kSuccess) << "Use the other constructor for successful layout"; @@ -52,7 +55,6 @@ NGLayoutResult::NGLayoutResult(const NGLayoutResult& other, base::Optional<LayoutUnit> bfc_block_offset) : space_(new_space), physical_fragment_(other.physical_fragment_), - oof_positioned_descendants_(other.oof_positioned_descendants_), unpositioned_list_marker_(other.unpositioned_list_marker_), exclusion_space_(MergeExclusionSpaces(other, space_.ExclusionSpace(), @@ -67,20 +69,23 @@ NGLayoutResult::NGLayoutResult(const NGLayoutResult& other, final_break_after_(other.final_break_after_), has_valid_space_(other.has_valid_space_), has_forced_break_(other.has_forced_break_), + is_self_collapsing_(other.is_self_collapsing_), is_pushed_by_floats_(other.is_pushed_by_floats_), adjoining_floats_(other.adjoining_floats_), - has_orthogonal_flow_roots_(other.has_orthogonal_flow_roots_), - may_have_descendant_above_block_start_( - other.may_have_descendant_above_block_start_), - depends_on_percentage_block_size_( - other.depends_on_percentage_block_size_), + is_initial_block_size_indefinite_( + other.is_initial_block_size_indefinite_), + has_descendant_that_depends_on_percentage_block_size_( + other.has_descendant_that_depends_on_percentage_block_size_), status_(other.status_) {} -NGLayoutResult::NGLayoutResult(NGContainerFragmentBuilder* builder, - bool cache_space) +NGLayoutResult::NGLayoutResult( + scoped_refptr<const NGPhysicalContainerFragment> physical_fragment, + NGContainerFragmentBuilder* builder, + bool cache_space) : space_(cache_space && builder->space_ ? NGConstraintSpace(*builder->space_) : NGConstraintSpace()), + physical_fragment_(std::move(physical_fragment)), unpositioned_list_marker_(builder->unpositioned_list_marker_), exclusion_space_(std::move(builder->exclusion_space_)), bfc_line_offset_(builder->bfc_line_offset_), @@ -88,56 +93,30 @@ NGLayoutResult::NGLayoutResult(NGContainerFragmentBuilder* builder, end_margin_strut_(builder->end_margin_strut_), has_valid_space_(cache_space && builder->space_), has_forced_break_(false), + is_self_collapsing_(builder->is_self_collapsing_), is_pushed_by_floats_(builder->is_pushed_by_floats_), adjoining_floats_(builder->adjoining_floats_), - has_orthogonal_flow_roots_(builder->has_orthogonal_flow_roots_), - may_have_descendant_above_block_start_( - builder->may_have_descendant_above_block_start_), - depends_on_percentage_block_size_(DependsOnPercentageBlockSize(*builder)), - status_(kSuccess) {} + is_initial_block_size_indefinite_(false), + has_descendant_that_depends_on_percentage_block_size_( + builder->has_descendant_that_depends_on_percentage_block_size_), + status_(kSuccess) { +#if DCHECK_IS_ON() + if (is_self_collapsing_ && physical_fragment_) { + // A new formatting-context shouldn't be self-collapsing. + DCHECK(!physical_fragment_->IsBlockFormattingContextRoot()); + + // Self-collapsing children must have a block-size of zero. + NGFragment fragment(physical_fragment_->Style().GetWritingMode(), + *physical_fragment_); + DCHECK_EQ(LayoutUnit(), fragment.BlockSize()); + } +#endif +} // Define the destructor here, so that we can forward-declare more in the // header. NGLayoutResult::~NGLayoutResult() = default; -bool NGLayoutResult::DependsOnPercentageBlockSize( - const NGContainerFragmentBuilder& builder) { - NGLayoutInputNode node = builder.node_; - - if (!node || node.IsInline()) - return builder.has_child_that_depends_on_percentage_block_size_; - - // NOTE: If an element is OOF positioned, and has top/bottom constraints - // which are percentage based, this function will return false. - // - // This is fine as the top/bottom constraints are computed *before* layout, - // and the result is set as a fixed-block-size constraint. (And the caching - // logic will never check the result of this function). - // - // The result of this function still may be used for an OOF positioned - // element if it has a percentage block-size however, but this will return - // the correct result from below. - - if ((builder.has_child_that_depends_on_percentage_block_size_ || - builder.is_old_layout_root_) && - node.UseParentPercentageResolutionBlockSizeForChildren()) { - // Quirks mode has different %-block-size behaviour, than standards mode. - // An arbitrary descendant may depend on the percentage resolution - // block-size given. - // If this is also an anonymous block we need to mark ourselves dependent - // if we have a dependent child. - return true; - } - - const ComputedStyle& style = builder.Style(); - if (style.LogicalHeight().IsPercentOrCalc() || - style.LogicalMinHeight().IsPercentOrCalc() || - style.LogicalMaxHeight().IsPercentOrCalc()) - return true; - - return false; -} - NGExclusionSpace NGLayoutResult::MergeExclusionSpaces( const NGLayoutResult& other, const NGExclusionSpace& new_input_exclusion_space, @@ -161,4 +140,44 @@ NGExclusionSpace NGLayoutResult::MergeExclusionSpaces( /* new_input */ new_input_exclusion_space, offset_delta); } +#if DCHECK_IS_ON() +void NGLayoutResult::CheckSameForSimplifiedLayout( + const NGLayoutResult& other, + bool check_same_block_size) const { + To<NGPhysicalBoxFragment>(*physical_fragment_) + .CheckSameForSimplifiedLayout( + To<NGPhysicalBoxFragment>(*other.physical_fragment_), + check_same_block_size); + + DCHECK(unpositioned_list_marker_ == other.unpositioned_list_marker_); + exclusion_space_.CheckSameForSimplifiedLayout(other.exclusion_space_); + + // We ignore bfc_block_offset_, and bfc_line_offset_ as "simplified" layout + // will move the layout result if required. + + // We ignore the intrinsic_block_size_ as if a scrollbar gets added/removed + // this may change (even if the size of the fragment remains the same). + + DCHECK(end_margin_strut_ == other.end_margin_strut_); + DCHECK_EQ(minimal_space_shortage_, other.minimal_space_shortage_); + + DCHECK_EQ(initial_break_before_, other.initial_break_before_); + DCHECK_EQ(final_break_after_, other.final_break_after_); + + DCHECK_EQ(has_valid_space_, other.has_valid_space_); + DCHECK_EQ(has_forced_break_, other.has_forced_break_); + DCHECK_EQ(is_self_collapsing_, other.is_self_collapsing_); + DCHECK_EQ(is_pushed_by_floats_, other.is_pushed_by_floats_); + DCHECK_EQ(adjoining_floats_, other.adjoining_floats_); + + if (check_same_block_size) { + DCHECK_EQ(is_initial_block_size_indefinite_, + other.is_initial_block_size_indefinite_); + } + DCHECK_EQ(has_descendant_that_depends_on_percentage_block_size_, + other.has_descendant_that_depends_on_percentage_block_size_); + DCHECK_EQ(status_, other.status_); +} +#endif + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.h index 12a9d223b90..e9aeb01dbee 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.h @@ -12,9 +12,9 @@ #include "third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h" #include "third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_link.h" -#include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_descendant.h" -#include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h" #include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/wtf/vector.h" @@ -49,16 +49,13 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { base::Optional<LayoutUnit> bfc_block_offset); ~NGLayoutResult(); - const NGPhysicalFragment* PhysicalFragment() const { - return physical_fragment_.get(); + const NGPhysicalContainerFragment& PhysicalFragment() const { + DCHECK(physical_fragment_); + DCHECK_EQ(NGLayoutResultStatus::kSuccess, Status()); + return *physical_fragment_; } - const Vector<NGOutOfFlowPositionedDescendant>& - OutOfFlowPositionedDescendants() const { - return oof_positioned_descendants_; - } - - NGLogicalOffset OutOfFlowPositionedOffset() const { + LogicalOffset OutOfFlowPositionedOffset() const { return oof_positioned_offset_; } @@ -99,6 +96,10 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { // Return true if the fragment broke because a forced break before a child. bool HasForcedBreak() const { return has_forced_break_; } + // Returns true if the fragment should be considered empty for margin + // collapsing purposes (e.g. margins "collapse through"). + bool IsSelfCollapsing() const { return is_self_collapsing_; } + // Return true if this fragment got its block offset increased by the presence // of floats. bool IsPushedByFloats() const { return is_pushed_by_floats_; } @@ -113,18 +114,19 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { // the block, and the block will fail to clear). NGFloatTypes AdjoiningFloatTypes() const { return adjoining_floats_; } - bool HasOrthogonalFlowRoots() const { return has_orthogonal_flow_roots_; } - - // Returns true if we aren't able to re-use this layout result if the - // PercentageResolutionBlockSize changes. - bool DependsOnPercentageBlockSize() const { - return depends_on_percentage_block_size_; + // Returns true if the initial (pre-layout) block-size of this fragment was + // indefinite. (e.g. it has "height: auto"). + bool IsInitialBlockSizeIndefinite() const { + return is_initial_block_size_indefinite_; } - // Returns true if we have a descendant within this formatting context, which - // is potentially above our block-start edge. - bool MayHaveDescendantAboveBlockStart() const { - return may_have_descendant_above_block_start_; + // Returns true if there is a descendant that depends on percentage + // resolution block-size changes. + // Some layout modes (flex-items, table-cells) have more complex child + // percentage sizing behaviour (typically when their parent layout forces a + // block-size on them). + bool HasDescendantThatDependsOnPercentageBlockSize() const { + return has_descendant_that_depends_on_percentage_block_size_; } // Returns true if the space stored with this layout result, is valid. @@ -144,7 +146,7 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { protected: friend class NGOutOfFlowLayoutPart; - void SetOutOfFlowPositionedOffset(const NGLogicalOffset& offset) { + void SetOutOfFlowPositionedOffset(const LogicalOffset& offset) { layout_result_->oof_positioned_offset_ = offset; } @@ -160,17 +162,24 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { return MutableForOutOfFlow(this); } +#if DCHECK_IS_ON() + void CheckSameForSimplifiedLayout(const NGLayoutResult&, + bool check_same_block_size = true) const; +#endif + private: friend class NGBoxFragmentBuilder; friend class NGLineBoxFragmentBuilder; friend class MutableForOutOfFlow; // This constructor requires a non-null fragment and sets a success status. - NGLayoutResult(scoped_refptr<const NGPhysicalFragment> physical_fragment, - NGBoxFragmentBuilder*); + NGLayoutResult( + scoped_refptr<const NGPhysicalContainerFragment> physical_fragment, + NGBoxFragmentBuilder*); // This constructor requires a non-null fragment and sets a success status. - NGLayoutResult(scoped_refptr<const NGPhysicalFragment> physical_fragment, - NGLineBoxFragmentBuilder*); + NGLayoutResult( + scoped_refptr<const NGPhysicalContainerFragment> physical_fragment, + NGLineBoxFragmentBuilder*); // This constructor is for a non-success status. NGLayoutResult(NGLayoutResultStatus, NGBoxFragmentBuilder*); @@ -179,9 +188,10 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { NGLayoutResult(const NGLayoutResult&) = delete; // Delegate constructor that sets up what it can, based on the builder. - NGLayoutResult(NGContainerFragmentBuilder* builder, bool cache_space); - - static bool DependsOnPercentageBlockSize(const NGContainerFragmentBuilder&); + NGLayoutResult( + scoped_refptr<const NGPhysicalContainerFragment> physical_fragment, + NGContainerFragmentBuilder* builder, + bool cache_space); static NGExclusionSpace MergeExclusionSpaces( const NGLayoutResult& other, @@ -193,14 +203,13 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { // as indicated by |has_valid_space_|. const NGConstraintSpace space_; - scoped_refptr<const NGPhysicalFragment> physical_fragment_; - Vector<NGOutOfFlowPositionedDescendant> oof_positioned_descendants_; + scoped_refptr<const NGPhysicalContainerFragment> physical_fragment_; // This is the final position of an OOF-positioned object in its parent's // writing-mode. This is set by the |NGOutOfFlowLayoutPart| while generating // this layout result. // This field is unused for other objects. - NGLogicalOffset oof_positioned_offset_; + LogicalOffset oof_positioned_offset_; NGUnpositionedListMarker unpositioned_list_marker_; const NGExclusionSpace exclusion_space_; @@ -216,12 +225,12 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { unsigned has_valid_space_ : 1; unsigned has_forced_break_ : 1; + unsigned is_self_collapsing_ : 1; unsigned is_pushed_by_floats_ : 1; - unsigned adjoining_floats_ : 2; // NGFloatTypes + unsigned adjoining_floats_ : 3; // NGFloatTypes - unsigned has_orthogonal_flow_roots_ : 1; - unsigned may_have_descendant_above_block_start_ : 1; - unsigned depends_on_percentage_block_size_ : 1; + unsigned is_initial_block_size_indefinite_ : 1; + unsigned has_descendant_that_depends_on_percentage_block_size_ : 1; unsigned status_ : 1; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc index 1a98c061319..688755f9290 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc @@ -2,8 +2,10 @@ // 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/geometry/ng_fragment_geometry.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_utils.h" namespace blink { namespace { @@ -42,11 +44,14 @@ TEST_F(NGLayoutResultCachingTest, HitDifferentExclusionSpace) { auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; const NGConstraintSpace& space = src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - scoped_refptr<const NGLayoutResult> result = - test->CachedLayoutResult(space, nullptr); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); EXPECT_NE(result.get(), nullptr); EXPECT_EQ(result->BfcBlockOffset().value(), LayoutUnit(50)); EXPECT_EQ(result->BfcLineOffset(), LayoutUnit()); @@ -82,11 +87,14 @@ TEST_F(NGLayoutResultCachingTest, HitDifferentBFCOffset) { auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; const NGConstraintSpace& space = src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - scoped_refptr<const NGLayoutResult> result = - test->CachedLayoutResult(space, nullptr); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); EXPECT_NE(result.get(), nullptr); EXPECT_EQ(result->BfcBlockOffset().value(), LayoutUnit(40)); EXPECT_EQ(result->BfcLineOffset(), LayoutUnit()); @@ -143,11 +151,14 @@ TEST_F(NGLayoutResultCachingTest, MissDescendantAboveBlockStart1) { auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; const NGConstraintSpace& space = src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - scoped_refptr<const NGLayoutResult> result = - test->CachedLayoutResult(space, nullptr); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); EXPECT_EQ(result.get(), nullptr); } @@ -180,14 +191,57 @@ TEST_F(NGLayoutResultCachingTest, MissDescendantAboveBlockStart2) { auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; const NGConstraintSpace& space = src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - scoped_refptr<const NGLayoutResult> result = - test->CachedLayoutResult(space, nullptr); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); EXPECT_EQ(result.get(), nullptr); } +TEST_F(NGLayoutResultCachingTest, HitOOFDescendantAboveBlockStart) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // Different BFC offset, same exclusion space, OOF-descendant above + // block start. + SetBodyInnerHTML(R"HTML( + <style> + .bfc { display: flow-root; width: 300px; height: 300px; } + .float { float: left; width: 50px; } + </style> + <div class="bfc"> + <div style="height: 50px;"> + <div class="float" style="height: 20px;"></div> + </div> + <div id="test" style="position: relative; height: 20px; padding-top: 5px;"> + <div style="position: absolute; height: 10px; top: -10px;"></div> + </div> + </div> + <div class="bfc"> + <div style="height: 40px;"> + <div class="float" style="height: 20px;"></div> + </div> + <div id="src" style="height: 20px;"></div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + TEST_F(NGLayoutResultCachingTest, MissFloatInitiallyIntruding1) { ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); @@ -215,11 +269,14 @@ TEST_F(NGLayoutResultCachingTest, MissFloatInitiallyIntruding1) { auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; const NGConstraintSpace& space = src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - scoped_refptr<const NGLayoutResult> result = - test->CachedLayoutResult(space, nullptr); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); EXPECT_EQ(result.get(), nullptr); } @@ -250,11 +307,14 @@ TEST_F(NGLayoutResultCachingTest, MissFloatInitiallyIntruding2) { auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; const NGConstraintSpace& space = src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - scoped_refptr<const NGLayoutResult> result = - test->CachedLayoutResult(space, nullptr); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); EXPECT_EQ(result.get(), nullptr); } @@ -284,11 +344,14 @@ TEST_F(NGLayoutResultCachingTest, MissFloatWillIntrude1) { auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; const NGConstraintSpace& space = src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - scoped_refptr<const NGLayoutResult> result = - test->CachedLayoutResult(space, nullptr); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); EXPECT_EQ(result.get(), nullptr); } @@ -318,15 +381,18 @@ TEST_F(NGLayoutResultCachingTest, MissFloatWillIntrude2) { auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; const NGConstraintSpace& space = src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - scoped_refptr<const NGLayoutResult> result = - test->CachedLayoutResult(space, nullptr); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); EXPECT_EQ(result.get(), nullptr); } -TEST_F(NGLayoutResultCachingTest, MissPushedByFloats1) { +TEST_F(NGLayoutResultCachingTest, HitPushedByFloats1) { ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); // Same BFC offset, different exclusion space, pushed by floats. @@ -343,7 +409,7 @@ TEST_F(NGLayoutResultCachingTest, MissPushedByFloats1) { </div> <div class="bfc"> <div style="height: 50px;"> - <div class="float" style="height: 40px;"></div> + <div class="float" style="height: 70px;"></div> </div> <div id="src" style="height: 20px; clear: left;"></div> </div> @@ -352,15 +418,18 @@ TEST_F(NGLayoutResultCachingTest, MissPushedByFloats1) { auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; const NGConstraintSpace& space = src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - scoped_refptr<const NGLayoutResult> result = - test->CachedLayoutResult(space, nullptr); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); - EXPECT_EQ(result.get(), nullptr); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); } -TEST_F(NGLayoutResultCachingTest, MissPushedByFloats2) { +TEST_F(NGLayoutResultCachingTest, HitPushedByFloats2) { ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); // Different BFC offset, same exclusion space, pushed by floats. @@ -386,11 +455,438 @@ TEST_F(NGLayoutResultCachingTest, MissPushedByFloats2) { auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, MissPushedByFloats1) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // Same BFC offset, different exclusion space, pushed by floats. + // Miss due to shrinking offset. + SetBodyInnerHTML(R"HTML( + <style> + .bfc { display: flow-root; width: 300px; height: 300px; } + .float { float: left; width: 50px; } + </style> + <div class="bfc"> + <div style="height: 50px;"> + <div class="float" style="height: 70px;"></div> + </div> + <div id="test" style="height: 20px; clear: left;"></div> + </div> + <div class="bfc"> + <div style="height: 50px;"> + <div class="float" style="height: 60px;"></div> + </div> + <div id="src" style="height: 20px; clear: left;"></div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); + EXPECT_EQ(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, MissPushedByFloats2) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // Different BFC offset, same exclusion space, pushed by floats. + // Miss due to shrinking offset. + SetBodyInnerHTML(R"HTML( + <style> + .bfc { display: flow-root; width: 300px; height: 300px; } + .float { float: left; width: 50px; } + </style> + <div class="bfc"> + <div style="height: 30px;"> + <div class="float" style="height: 60px;"></div> + </div> + <div id="test" style="height: 20px; clear: left;"></div> + </div> + <div class="bfc"> + <div style="height: 50px;"> + <div class="float" style="height: 60px;"></div> + </div> + <div id="src" style="height: 20px; clear: left;"></div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; const NGConstraintSpace& space = src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - scoped_refptr<const NGLayoutResult> result = - test->CachedLayoutResult(space, nullptr); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); + EXPECT_EQ(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, HitDifferentRareData) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // Same absolute fixed constraints. + SetBodyInnerHTML(R"HTML( + <style> + .container { position: relative; width: 100px; height: 100px; } + .abs { position: absolute; width: 100px; height: 100px; top: 0; left: 0; } + </style> + <div class="container"> + <div id="test" class="abs"></div> + </div> + <div class="container" style="width: 200px; height: 200px;"> + <div id="src" class="abs"></div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, HitPercentageMinWidth) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // min-width calculates to different values, but doesn't change size. + SetBodyInnerHTML(R"HTML( + <style> + .bfc { display: flow-root; width: 300px; height: 300px; } + .inflow { width: 100px; min-width: 25%; } + </style> + <div class="bfc"> + <div id="test" class="inflow"></div> + </div> + <div class="bfc" style="width: 200px; height: 200px;"> + <div id="src" class="inflow"></div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, HitFixedMinWidth) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // min-width is always larger than the available size. + SetBodyInnerHTML(R"HTML( + <style> + .bfc { display: flow-root; width: 300px; height: 300px; } + .inflow { min-width: 300px; } + </style> + <div class="bfc"> + <div id="test" class="inflow"></div> + </div> + <div class="bfc" style="width: 200px; height: 200px;"> + <div id="src" class="inflow"></div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, HitShrinkToFitSameIntrinsicSizes) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // We have a shrink-to-fit node, with the min, and max intrinsic sizes being + // equal (the available size doesn't affect the final size). + SetBodyInnerHTML(R"HTML( + <style> + .bfc { display: flow-root; width: 300px; height: 300px; } + .shrink { width: fit-content; } + .child { width: 250px; } + </style> + <div class="bfc"> + <div id="test" class="shrink"> + <div class="child"></div> + </div> + </div> + <div class="bfc" style="width: 200px; height: 200px;"> + <div id="src" class="shrink"> + <div class="child"></div> + </div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, HitShrinkToFitDifferentParent) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // The parent "bfc" node changes from shrink-to-fit, to a fixed width. But + // these calculate as the same available space to the "test" element. + SetBodyInnerHTML(R"HTML( + <style> + .bfc { display: flow-root; } + .child { width: 250px; } + </style> + <div class="bfc" style="width: fit-content; height: 100px;"> + <div id="test"> + <div class="child"></div> + </div> + </div> + <div class="bfc" style="width: 250px; height: 100px;"> + <div id="src"> + <div class="child"></div> + </div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, MissQuirksModePercentageBasedChild) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // Quirks-mode %-block-size child. + GetDocument().SetCompatibilityMode(Document::kQuirksMode); + SetBodyInnerHTML(R"HTML( + <style> + .bfc { display: flow-root; width: 300px; height: 300px; } + .child { height: 50%; } + </style> + <div class="bfc"> + <div id="test"> + <div class="child"></div> + </div> + </div> + <div class="bfc" style="height: 200px;"> + <div id="src"> + <div class="child"></div> + </div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); + EXPECT_EQ(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, HitQuirksModePercentageBasedParentAndChild) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // Quirks-mode %-block-size parent *and* child. Here we mark the parent as + // depending on %-block-size changes, however itself doesn't change in + // height. + // We are able to hit the cache as we detect that the height for the child + // *isn't* indefinite, and results in the same height as before. + GetDocument().SetCompatibilityMode(Document::kQuirksMode); + SetBodyInnerHTML(R"HTML( + <style> + .bfc { display: flow-root; width: 300px; height: 300px; } + .parent { height: 50%; min-height: 200px; } + .child { height: 50%; } + </style> + <div class="bfc"> + <div id="test" class="parent"> + <div class="child"></div> + </div> + </div> + <div class="bfc" style="height: 200px;"> + <div id="src" class="parent"> + <div class="child"></div> + </div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, HitStandardsModePercentageBasedChild) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + // Standards-mode %-block-size child. + SetBodyInnerHTML(R"HTML( + <style> + .bfc { display: flow-root; width: 300px; height: 300px; } + .child { height: 50%; } + </style> + <div class="bfc"> + <div id="test"> + <div class="child"></div> + </div> + </div> + <div class="bfc" style="height: 200px;"> + <div id="src"> + <div class="child"></div> + </div> + </div> + )HTML"); + + auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test")); + auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + const NGConstraintSpace& space = + src->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, ChangeTableCellBlockSizeConstrainedness) { + ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); + + SetBodyInnerHTML(R"HTML( + <style> + .table { display: table; width: 300px; } + .cell { display: table-cell; } + .child1 { height: 100px; } + .child2, .child3 { overflow:auto; height:10%; } + </style> + <div class="table"> + <div class="cell"> + <div class="child1" id="test1"></div> + <div class="child2" id="test2"> + <div style="height:30px;"></div> + </div> + <div class="child3" id="test3"></div> + </div> + </div> + <div class="table" style="height:300px;"> + <div class="cell"> + <div class="child1" id="src1"></div> + <div class="child2" id="src2"> + <div style="height:30px;"></div> + </div> + <div class="child3" id="src3"></div> + </div> + </div> + )HTML"); + + auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1")); + auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2")); + auto* test3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test3")); + auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1")); + auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2")); + auto* src3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src3")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + NGConstraintSpace space = + src1->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test1->CachedLayoutResult( + space, nullptr, &fragment_geometry, &cache_status); + // The first child has a fixed height, and shouldn't be affected by the cell + // height. + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); + + fragment_geometry.reset(); + space = src2->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + result = test2->CachedLayoutResult(space, nullptr, &fragment_geometry, + &cache_status); + // The second child has overflow:auto and a percentage height, but its + // intrinsic height is identical to its extrinsic height (when the cell has a + // height). So it won't need layout, either. + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); + fragment_geometry.reset(); + space = src3->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + result = test3->CachedLayoutResult(space, nullptr, &fragment_geometry, + &cache_status); + // The third child has overflow:auto and a percentage height, and its + // intrinsic height is 0 (no children), so it matters whether the cell has a + // height or not. We're only going to need simplified layout, though, since no + // children will be affected by its height change. + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsSimplifiedLayout); EXPECT_EQ(result.get(), nullptr); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc index fce051a0d30..165908d0206 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc @@ -4,69 +4,22 @@ #include "third_party/blink/renderer/core/layout/ng/ng_layout_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" -#include "third_party/blink/renderer/core/layout/ng/ng_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.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" namespace blink { namespace { -bool ContentShrinkToFitMayChange(const ComputedStyle& style, - const NGConstraintSpace& new_space, - const NGConstraintSpace& old_space, - const NGLayoutResult& layout_result) { - if (old_space.AvailableSize().inline_size == - new_space.AvailableSize().inline_size) - return false; - - NGBoxStrut margins = ComputeMarginsForSelf(new_space, style); - -#if DCHECK_IS_ON() - // The margins must be the same, as this function won't be called if we have - // percentage inline margins, and the percentage resolution size changes. - NGBoxStrut old_margins = ComputeMarginsForSelf(old_space, style); - DCHECK_EQ(margins.inline_start, old_margins.inline_start); - DCHECK_EQ(margins.inline_end, old_margins.inline_end); -#endif - - LayoutUnit old_available_inline_size = - std::max(LayoutUnit(), - old_space.AvailableSize().inline_size - margins.InlineSum()); - LayoutUnit new_available_inline_size = - std::max(LayoutUnit(), - new_space.AvailableSize().inline_size - margins.InlineSum()); - - DCHECK(layout_result.PhysicalFragment()); - LayoutUnit inline_size = - NGFragment(style.GetWritingMode(), *layout_result.PhysicalFragment()) - .InlineSize(); - - // If the previous fragment was at its min-content size (indicated by the old - // available size being smaller than the fragment), we may be able to skip - // layout if the new available size is also smaller. - bool unaffected_as_min_content_size = - old_available_inline_size < inline_size && - new_available_inline_size <= inline_size; - - // If the previous fragment was at its max-content size (indicated by the old - // available size being larger than the fragment), we may be able to skip - // layout if the new available size is also larger. - bool unaffected_as_max_content_size = - old_available_inline_size > inline_size && - new_available_inline_size >= inline_size; - - // TODO(crbug.com/935634): There is an additional optimization where if we - // detect (by setting a flag in the layout result) that the - // min-content == max-content we can simply just skip layout, as the - // available size won't have any effect. - - if (unaffected_as_min_content_size || unaffected_as_max_content_size) - return false; - - return true; -} +// LengthResolveType indicates what type length the function is being passed +// based on its CSS property. E.g. +// kMinSize - min-width / min-height +// kMaxSize - max-width / max-height +// kMainSize - width / height +enum class LengthResolveType { kMinSize, kMaxSize, kMainSize }; inline bool InlineLengthMayChange(const ComputedStyle& style, const Length& length, @@ -93,18 +46,6 @@ inline bool InlineLengthMayChange(const ComputedStyle& style, old_space.PercentageResolutionInlineSize())) return true; - // For elements which shrink to fit, we can perform a specific optimization - // where we can skip relayout if the element was sized to its min-content or - // max-content size. - bool is_content_shrink_to_fit = - type == LengthResolveType::kMainSize && - (new_space.IsShrinkToFit() || length.IsFitContent()); - - if (is_content_shrink_to_fit) { - return ContentShrinkToFitMayChange(style, new_space, old_space, - layout_result); - } - if (is_unspecified) { if (new_space.AvailableSize().inline_size != old_space.AvailableSize().inline_size) @@ -127,18 +68,27 @@ inline bool BlockLengthMayChange(const Length& length, old_space.AvailableSize().block_size) return true; } + return false; } // Return true if it's possible (but not necessarily guaranteed) that the new // constraint space will give a different size compared to the old one, when // computed style and child content remain unchanged. -bool SizeMayChange(const ComputedStyle& style, +bool SizeMayChange(const NGBlockNode& node, const NGConstraintSpace& new_space, const NGConstraintSpace& old_space, const NGLayoutResult& layout_result) { + if (node.IsQuirkyAndFillsViewport()) + return true; + DCHECK_EQ(new_space.IsFixedSizeInline(), old_space.IsFixedSizeInline()); DCHECK_EQ(new_space.IsFixedSizeBlock(), old_space.IsFixedSizeBlock()); + DCHECK_EQ(new_space.IsShrinkToFit(), old_space.IsShrinkToFit()); + DCHECK_EQ(new_space.TableCellChildLayoutPhase(), + old_space.TableCellChildLayoutPhase()); + + const ComputedStyle& style = node.Style(); // Go through all length properties, and, depending on length type // (percentages, auto, etc.), check whether the constraint spaces differ in @@ -178,7 +128,7 @@ bool SizeMayChange(const ComputedStyle& style, return true; // We only need to check if the PercentageResolutionBlockSizes match if the // layout result has explicitly marked itself as dependent. - if (layout_result.DependsOnPercentageBlockSize()) { + if (layout_result.PhysicalFragment().DependsOnPercentageBlockSize()) { if (new_space.PercentageResolutionBlockSize() != old_space.PercentageResolutionBlockSize()) return true; @@ -203,11 +153,198 @@ bool SizeMayChange(const ComputedStyle& style, return false; } +// Given the pre-computed |fragment_geometry| calcuates the +// |NGLayoutCacheStatus| based on this sizing information. Returns: +// - |NGLayoutCacheStatus::kNeedsLayout| if the |new_space| will produce a +// different sized fragment, or if any %-block-size children will change +// size. +// - |NGLayoutCacheStatus::kNeedsSimplifiedLayout| if the block-size of the +// fragment will change, *without* affecting any descendants (no descendants +// have %-block-sizes). +// - |NGLayoutCacheStatus::kHit| otherwise. +NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry( + const NGBlockNode& node, + const NGFragmentGeometry& fragment_geometry, + const NGLayoutResult& layout_result, + const NGConstraintSpace& new_space, + const NGConstraintSpace& old_space) { + const ComputedStyle& style = node.Style(); + const NGPhysicalBoxFragment& physical_fragment = + To<NGPhysicalBoxFragment>(layout_result.PhysicalFragment()); + NGBoxFragment fragment(style.GetWritingMode(), style.Direction(), + physical_fragment); + + if (fragment_geometry.border_box_size.inline_size != fragment.InlineSize()) + return NGLayoutCacheStatus::kNeedsLayout; + + LayoutUnit block_size = fragment_geometry.border_box_size.block_size; + bool is_initial_block_size_indefinite = block_size == kIndefiniteSize; + if (is_initial_block_size_indefinite) { + // The intrinsic size of column flex-boxes can depend on the + // %-resolution-block-size. This occurs when a flex-box has "max-height: + // 100%" or similar on itself. + // + // Due to this we can't use cached |NGLayoutResult::IntrinsicBlockSize| + // value, as the following |block_size| calculation would be incorrect. + if (node.IsFlexibleBox() && style.IsColumnFlexDirection() && + layout_result.PhysicalFragment().DependsOnPercentageBlockSize()) { + if (new_space.PercentageResolutionBlockSize() != + old_space.PercentageResolutionBlockSize()) + return NGLayoutCacheStatus::kNeedsLayout; + } + + block_size = ComputeBlockSizeForFragment( + new_space, node, fragment_geometry.border + fragment_geometry.padding, + layout_result.IntrinsicBlockSize()); + } + + bool is_block_size_equal = block_size == fragment.BlockSize(); + + if (!is_block_size_equal) { + // If we are the document or body element in quirks mode, changing our size + // means that a scrollbar was added/removed. Require full layout. + if (node.IsQuirkyAndFillsViewport()) + return NGLayoutCacheStatus::kNeedsLayout; + + // If a block (within a formatting-context) changes to/from an empty-block, + // margins may collapse through this node, requiring full layout. We + // approximate this check by checking if the block-size is/was zero. + if (!physical_fragment.IsBlockFormattingContextRoot() && + !block_size != !fragment.BlockSize()) + return NGLayoutCacheStatus::kNeedsLayout; + } + + if (layout_result.HasDescendantThatDependsOnPercentageBlockSize()) { + // %-block-size children of flex-items sometimes don't resolve their + // percentages against a fixed block-size. + // We miss the cache if the %-resolution block-size changes from indefinite + // to definite (or visa-versa). + bool is_new_initial_block_size_indefinite = + new_space.IsFixedSizeBlock() ? !new_space.FixedSizeBlockIsDefinite() + : is_initial_block_size_indefinite; + + bool is_old_initial_block_size_indefinite = + old_space.IsFixedSizeBlock() + ? !old_space.FixedSizeBlockIsDefinite() + : layout_result.IsInitialBlockSizeIndefinite(); + + if (is_old_initial_block_size_indefinite != + is_new_initial_block_size_indefinite) + return NGLayoutCacheStatus::kNeedsLayout; + + // %-block-size children of table-cells have different behaviour if they + // are in the "measure" or "layout" phase. + // Instead of trying to capture that logic here, we always miss the cache. + if (node.IsTableCell() && + new_space.IsFixedSizeBlock() != old_space.IsFixedSizeBlock()) + return NGLayoutCacheStatus::kNeedsLayout; + + // If our initial block-size is definite, we know that if we change our + // block-size we'll affect any descendant that depends on the resulting + // percentage block-size. + if (!is_block_size_equal && !is_new_initial_block_size_indefinite) + return NGLayoutCacheStatus::kNeedsLayout; + + DCHECK(is_block_size_equal || is_new_initial_block_size_indefinite); + + // At this point we know that either we have the same block-size for our + // fragment, or our initial block-size was indefinite. + // + // The |NGPhysicalContainerFragment::DependsOnPercentageBlockSize| flag + // will returns true if we are in quirks mode, and have a descendant that + // depends on a percentage block-size, however it will also return true if + // the node itself depends on the %-block-size. + // + // As we only care about the quirks-mode %-block-size behaviour we remove + // this false-positive by checking if we have an initial indefinite + // block-size. + if (is_new_initial_block_size_indefinite && + layout_result.PhysicalFragment().DependsOnPercentageBlockSize()) { + DCHECK(is_old_initial_block_size_indefinite); + if (new_space.PercentageResolutionBlockSize() != + old_space.PercentageResolutionBlockSize()) + return NGLayoutCacheStatus::kNeedsLayout; + if (new_space.ReplacedPercentageResolutionBlockSize() != + old_space.ReplacedPercentageResolutionBlockSize()) + return NGLayoutCacheStatus::kNeedsLayout; + } + } + + if (style.MayHavePadding() && fragment_geometry.padding != fragment.Padding()) + return NGLayoutCacheStatus::kNeedsLayout; + + // If we've reached here we know that we can potentially "stretch"/"shrink" + // ourselves without affecting any of our children. + // In that case we may be able to perform "simplified" layout. + return is_block_size_equal ? NGLayoutCacheStatus::kHit + : NGLayoutCacheStatus::kNeedsSimplifiedLayout; +} + +bool IntrinsicSizeWillChange( + const NGBlockNode& node, + const NGLayoutResult& cached_layout_result, + const NGConstraintSpace& new_space, + base::Optional<NGFragmentGeometry>* fragment_geometry) { + const ComputedStyle& style = node.Style(); + if (!new_space.IsShrinkToFit() && !NeedMinMaxSize(style)) + return false; + + if (!*fragment_geometry) + *fragment_geometry = CalculateInitialFragmentGeometry(new_space, node); + + LayoutUnit inline_size = NGFragment(style.GetWritingMode(), + cached_layout_result.PhysicalFragment()) + .InlineSize(); + + if ((*fragment_geometry)->border_box_size.inline_size != inline_size) + return true; + + return false; +} + } // namespace -bool MaySkipLayout(const NGBlockNode node, - const NGLayoutResult& cached_layout_result, - const NGConstraintSpace& new_space) { +NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatus( + const NGBlockNode& node, + const NGLayoutResult& cached_layout_result, + const NGConstraintSpace& new_space, + base::Optional<NGFragmentGeometry>* fragment_geometry) { + DCHECK_EQ(cached_layout_result.Status(), NGLayoutResult::kSuccess); + DCHECK(cached_layout_result.HasValidConstraintSpaceForCaching()); + + const NGConstraintSpace& old_space = + cached_layout_result.GetConstraintSpaceForCaching(); + + if (!new_space.MaySkipLayout(old_space)) + return NGLayoutCacheStatus::kNeedsLayout; + + if (new_space.AreSizeConstraintsEqual(old_space)) { + // It is possible that our intrinsic size has changed, check for that here. + // TODO(cbiesinger): Investigate why this check doesn't apply to + // |MaySkipLegacyLayout|. + if (IntrinsicSizeWillChange(node, cached_layout_result, new_space, + fragment_geometry)) + return NGLayoutCacheStatus::kNeedsLayout; + + // We don't have to check our style if we know the constraint space sizes + // will remain the same. + if (new_space.AreSizesEqual(old_space)) + return NGLayoutCacheStatus::kHit; + + if (!SizeMayChange(node, new_space, old_space, cached_layout_result)) + return NGLayoutCacheStatus::kHit; + } + + if (!*fragment_geometry) + *fragment_geometry = CalculateInitialFragmentGeometry(new_space, node); + + return CalculateSizeBasedLayoutCacheStatusWithGeometry( + node, **fragment_geometry, cached_layout_result, new_space, old_space); +} + +bool MaySkipLegacyLayout(const NGBlockNode& node, + const NGLayoutResult& cached_layout_result, + const NGConstraintSpace& new_space) { DCHECK_EQ(cached_layout_result.Status(), NGLayoutResult::kSuccess); DCHECK(cached_layout_result.HasValidConstraintSpaceForCaching()); @@ -216,15 +353,89 @@ bool MaySkipLayout(const NGBlockNode node, if (!new_space.MaySkipLayout(old_space)) return false; - if (!new_space.AreSizesEqual(old_space)) { - // We need to descend all the way down into BODY if we're in quirks mode, - // since it magically follows the viewport size. - if (node.IsQuirkyAndFillsViewport()) + if (!new_space.AreSizeConstraintsEqual(old_space)) + return false; + + if (new_space.AreSizesEqual(old_space)) + return true; + + if (SizeMayChange(node, new_space, old_space, cached_layout_result)) + return false; + + return true; +} + +bool MaySkipLayoutWithinBlockFormattingContext( + const NGLayoutResult& cached_layout_result, + const NGConstraintSpace& new_space, + base::Optional<LayoutUnit>* bfc_block_offset) { + DCHECK_EQ(cached_layout_result.Status(), NGLayoutResult::kSuccess); + DCHECK(cached_layout_result.HasValidConstraintSpaceForCaching()); + DCHECK(bfc_block_offset); + + const NGConstraintSpace& old_space = + cached_layout_result.GetConstraintSpaceForCaching(); + + LayoutUnit old_clearance_offset = old_space.ClearanceOffset(); + LayoutUnit new_clearance_offset = new_space.ClearanceOffset(); + + // Determine if we can reuse a result if it was affected by clearance. + bool is_pushed_by_floats = cached_layout_result.IsPushedByFloats(); + if (is_pushed_by_floats) { + DCHECK(old_space.HasFloats()); + + // We don't attempt to reuse the cached result if the clearance offset + // differs from the final BFC-block-offset. + // + // The |is_pushed_by_floats| flag is also used by nodes who have a *child* + // which was pushed by floats. In this case the node may not have a + // BFC-block-offset or one equal to the clearance offset. + if (!cached_layout_result.BfcBlockOffset() || + *cached_layout_result.BfcBlockOffset() != old_space.ClearanceOffset()) return false; - // If the available / percentage sizes have changed in a way that may affect - // layout, we cannot re-use the previous result. - if (SizeMayChange(node.Style(), new_space, old_space, cached_layout_result)) + // We only reuse the cached result if the delta between the + // BFC-block-offset, and the clearance offset grows or remains the same. If + // it shrinks it may not be affected by clearance anymore as a margin may + // push the fragment below the clearance offset instead. + // + // TODO(layout-dev): If we track if any margins affected this calculation + // (with an additional bit on the layout result) we could potentially skip + // this check. + if (old_clearance_offset - old_space.BfcOffset().block_offset > + new_clearance_offset - new_space.BfcOffset().block_offset) { + return false; + } + } + + // Check we have a descendant that *may* be positioned above the block-start + // edge. We abort if either the old or new space has floats, as we don't keep + // track of how far above the child could be. This case is relatively rare, + // and only occurs with negative margins. + if (cached_layout_result.PhysicalFragment() + .MayHaveDescendantAboveBlockStart() && + (old_space.HasFloats() || new_space.HasFloats())) + return false; + + // We can now try to adjust the BFC block-offset. + if (*bfc_block_offset) { + // Check if the previous position may intersect with any floats. + if (**bfc_block_offset < + old_space.ExclusionSpace().ClearanceOffset(EClear::kBoth)) + return false; + + if (is_pushed_by_floats) { + DCHECK_EQ(**bfc_block_offset, old_clearance_offset); + *bfc_block_offset = new_clearance_offset; + } else { + *bfc_block_offset = **bfc_block_offset - + old_space.BfcOffset().block_offset + + new_space.BfcOffset().block_offset; + } + + // Check if the new position may intersect with any floats. + if (**bfc_block_offset < + new_space.ExclusionSpace().ClearanceOffset(EClear::kBoth)) return false; } @@ -238,8 +449,7 @@ bool IsBlockLayoutComplete(const NGConstraintSpace& space, if (space.IsIntermediateLayout()) return false; // Check that we're done positioning pending floats. - return !result.AdjoiningFloatTypes() || result.BfcBlockOffset() || - space.FloatsBfcBlockOffset(); + return !result.AdjoiningFloatTypes() || result.BfcBlockOffset(); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.h index 1a880d95cb9..b37bdc7eaf9 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.h @@ -12,12 +12,48 @@ namespace blink { class NGConstraintSpace; class NGLayoutResult; -// Returns true if for a given |new_space|, the |node| will provide the same -// |NGLayoutResult| as |cached_layout_result|, and therefore might be able to -// skip layout. -bool MaySkipLayout(const NGBlockNode node, - const NGLayoutResult& cached_layout_result, - const NGConstraintSpace& new_space); +// NGLayoutCacheStatus indicates what type of cache hit/miss occurred. For +// various types of misses we may be able to perform less work than a full +// layout. +// +// See |NGSimplifiedLayoutAlgorithm| for details about the +// |kNeedsSimplifiedLayout| cache miss type. +enum class NGLayoutCacheStatus { + kHit, // Cache hit, no additional work required. + kNeedsLayout, // Cache miss, full layout required. + kNeedsSimplifiedLayout // Cache miss, simplified layout required. +}; + +// Calculates the |NGLayoutCacheStatus| based on sizing information. Returns: +// - |NGLayoutCacheStatus::kHit| if the size will be the same as +// |cached_layout_result|, and therefore might be able to skip layout. +// - |NGLayoutCacheStatus::kNeedsSimplifiedLayout| if a simplified layout may +// be possible (just based on the sizing information at this point). +// - |NGLayoutCacheStatus::kNeedsLayout| if a full layout is required. +// +// May pre-compute the |fragment_geometry| while calculating this status. +NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatus( + const NGBlockNode& node, + const NGLayoutResult& cached_layout_result, + const NGConstraintSpace& new_space, + base::Optional<NGFragmentGeometry>* fragment_geometry); + +// Similar to |MaySkipLayout| but for legacy layout roots. Doesn't attempt to +// pre-compute the geometry of the fragment. +bool MaySkipLegacyLayout(const NGBlockNode& node, + const NGLayoutResult& cached_layout_result, + const NGConstraintSpace& new_space); + +// Returns true if for a given |new_space|, the |cached_layout_result| won't be +// affected by clearance, or floats, and therefore might be able to skip +// layout. +// Additionally (if this function returns true) it will calculate the new +// |bfc_block_offset| for the layout result. This may still be |base::nullopt| +// if not previously set. +bool MaySkipLayoutWithinBlockFormattingContext( + const NGLayoutResult& cached_layout_result, + const NGConstraintSpace& new_space, + base::Optional<LayoutUnit>* bfc_block_offset); // Return true if layout is considered complete. In some cases we require more // than one layout pass. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc index 8f167e9b57f..b3107708039 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc @@ -1,4 +1,4 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. +// 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. @@ -97,12 +97,12 @@ bool BlockLengthUnresolvable( opt_percentage_resolution_block_size_for_min_max ? *opt_percentage_resolution_block_size_for_min_max : constraint_space.PercentageResolutionBlockSize(); - return percentage_resolution_block_size == NGSizeIndefinite; + return percentage_resolution_block_size == kIndefiniteSize; } if (length.IsFillAvailable()) { return phase == LengthResolvePhase::kIntrinsic || - constraint_space.AvailableSize().block_size == NGSizeIndefinite; + constraint_space.AvailableSize().block_size == kIndefiniteSize; } return false; @@ -253,7 +253,7 @@ MinMaxSize ComputeMinAndMaxContentContribution( .ToConstraintSpace(); LayoutUnit content_size = - min_and_max ? min_and_max->max_size : NGSizeIndefinite; + min_and_max ? min_and_max->max_size : kIndefiniteSize; MinMaxSize computed_sizes; const Length& inline_size = parent_writing_mode == WritingMode::kHorizontalTb @@ -380,17 +380,26 @@ MinMaxSize ComputeMinAndMaxContentSizeForOutOfFlow( const NGBoxStrut& border_padding, const MinMaxSizeInput& input) { LayoutBox* box = node.GetLayoutBox(); - if (!box->PreferredLogicalWidthsDirty() && - !box->NeedsPreferredWidthsRecalculation()) { + // If we've already populated the legacy preferred logical widths cache for + // this node at the bottom of this function, use those cached results here. + // Or, if we're working on a table, use the legacy preferred widths code + // instead of ComputeMinAndMaxContentContribution below because + // ComputeMinAndMaxContentContribution assumes that if an element has a + // specified size, that's its final size, which tables don't follow. + if ((!box->PreferredLogicalWidthsDirty() && + !box->NeedsPreferredWidthsRecalculation()) || + box->IsTable()) { return MinMaxSize{box->MinPreferredLogicalWidth(), box->MaxPreferredLogicalWidth()}; } + // Compute the intrinsic sizes without regard to the specified sizes. MinMaxSize result = node.ComputeMinMaxSize(node.Style().GetWritingMode(), input, &constraint_space); - // Cache these computed values. + // Apply the specified min, main, max sizes. MinMaxSize contribution = ComputeMinAndMaxContentContribution( node.Style().GetWritingMode(), node.Style(), border_padding, result); + // Cache these computed values. box->SetPreferredLogicalWidthsFromNG(contribution); return result; } @@ -472,23 +481,36 @@ LayoutUnit ComputeBlockSizeForFragmentInternal( LayoutUnit content_size, const LayoutUnit* opt_percentage_resolution_block_size_for_min_max = nullptr) { + LayoutUnit min = ResolveMinBlockLength( + constraint_space, style, border_padding, style.LogicalMinHeight(), + content_size, LengthResolvePhase::kLayout, + opt_percentage_resolution_block_size_for_min_max); const Length& logical_height = style.LogicalHeight(); // Scrollable percentage-sized children of table cells, in the table // "measure" phase contribute nothing to the row height measurement. // See: https://drafts.csswg.org/css-tables-3/#row-layout + // We only apply this rule if the block size of the containing table cell is + // considered to be restricted, though. Otherwise, especially if this is the + // only child of the cell, and that is the only cell in the row, we'd end up + // with zero block size. To match the legacy layout engine behavior in + // LayoutBox::ContainingBlockLogicalHeightForPercentageResolution(), we only + // check the block-size of the containing cell and its containing table. Other + // things to consider, would be checking the row and row-group, and also other + // properties, such as {min,max}-block-size. if (logical_height.IsPercentOrCalc() && constraint_space.TableCellChildLayoutPhase() == NGTableCellChildLayoutPhase::kMeasure && (style.OverflowY() == EOverflow::kAuto || - style.OverflowY() == EOverflow::kScroll)) - return border_padding.BlockSum(); + style.OverflowY() == EOverflow::kScroll) && + constraint_space.IsInRestrictedBlockSizeTableCell()) + return min; LayoutUnit extent = ResolveMainBlockLength( constraint_space, style, border_padding, logical_height, content_size, LengthResolvePhase::kLayout, opt_percentage_resolution_block_size_for_min_max); - if (extent == NGSizeIndefinite) { - DCHECK_EQ(content_size, NGSizeIndefinite); + if (extent == kIndefiniteSize) { + DCHECK_EQ(content_size, kIndefiniteSize); return extent; } @@ -496,10 +518,6 @@ LayoutUnit ComputeBlockSizeForFragmentInternal( constraint_space, style, border_padding, style.LogicalMaxHeight(), content_size, LengthResolvePhase::kLayout, opt_percentage_resolution_block_size_for_min_max); - LayoutUnit min = ResolveMinBlockLength( - constraint_space, style, border_padding, style.LogicalMinHeight(), - content_size, LengthResolvePhase::kLayout, - opt_percentage_resolution_block_size_for_min_max); return ConstrainByMinMax(extent, min, max); } @@ -508,21 +526,25 @@ LayoutUnit ComputeBlockSizeForFragmentInternal( LayoutUnit ComputeBlockSizeForFragment( const NGConstraintSpace& constraint_space, - const ComputedStyle& style, + const NGBlockNode& node, const NGBoxStrut& border_padding, LayoutUnit content_size) { + // The final block-size of a table-cell is always its intrinsic size. + if (node.IsTableCell() && content_size != kIndefiniteSize) + return content_size; + if (constraint_space.IsFixedSizeBlock()) return constraint_space.AvailableSize().block_size; if (constraint_space.IsAnonymous()) return content_size; - return ComputeBlockSizeForFragmentInternal(constraint_space, style, + return ComputeBlockSizeForFragmentInternal(constraint_space, node.Style(), border_padding, content_size); } // Computes size for a replaced element. -NGLogicalSize ComputeReplacedSize( +LogicalSize ComputeReplacedSize( const NGLayoutInputNode& node, const NGConstraintSpace& space, const base::Optional<MinMaxSize>& child_minmax) { @@ -561,11 +583,11 @@ NGLogicalSize ComputeReplacedSize( replaced_block = ConstrainByMinMax(*replaced_block, block_min, block_max); } if (replaced_inline && replaced_block) - return NGLogicalSize(*replaced_inline, *replaced_block); + return LogicalSize(*replaced_inline, *replaced_block); base::Optional<LayoutUnit> intrinsic_inline; base::Optional<LayoutUnit> intrinsic_block; - NGLogicalSize aspect_ratio; + LogicalSize aspect_ratio; node.IntrinsicSize(&intrinsic_inline, &intrinsic_block, &aspect_ratio); // Computing intrinsic size is complicated by the fact that @@ -682,14 +704,14 @@ NGLogicalSize ComputeReplacedSize( } } } - return NGLogicalSize(*replaced_inline, *replaced_block); + return LogicalSize(*replaced_inline, *replaced_block); } int ResolveUsedColumnCount(int computed_count, LayoutUnit computed_size, LayoutUnit used_gap, LayoutUnit available_size) { - if (computed_size == NGSizeIndefinite) { + if (computed_size == kIndefiniteSize) { DCHECK(computed_count); return computed_count; } @@ -706,7 +728,7 @@ int ResolveUsedColumnCount(LayoutUnit available_size, const ComputedStyle& style) { LayoutUnit computed_column_inline_size = style.HasAutoColumnWidth() - ? NGSizeIndefinite + ? kIndefiniteSize : std::max(LayoutUnit(1), LayoutUnit(style.ColumnWidth())); LayoutUnit gap = ResolveUsedColumnGap(available_size, style); int computed_count = style.ColumnCount(); @@ -731,7 +753,7 @@ LayoutUnit ResolveUsedColumnInlineSize(LayoutUnit available_size, LayoutUnit computed_size = style.HasAutoColumnWidth() - ? NGSizeIndefinite + ? kIndefiniteSize : std::max(LayoutUnit(1), LayoutUnit(style.ColumnWidth())); int computed_count = style.HasAutoColumnCount() ? 0 : style.ColumnCount(); LayoutUnit used_gap = ResolveUsedColumnGap(available_size, style); @@ -872,6 +894,22 @@ NGBoxStrut ComputePadding(const NGConstraintSpace& constraint_space, return padding; } +NGBoxStrut ComputeScrollbars(const NGConstraintSpace& constraint_space, + const NGLayoutInputNode node) { + const ComputedStyle& style = node.Style(); + if (constraint_space.IsAnonymous() || style.IsOverflowVisible()) + return NGBoxStrut(); + NGPhysicalBoxStrut sizes; + const LayoutBox* layout_box = node.GetLayoutBox(); + LayoutUnit horizontal = LayoutUnit(layout_box->HorizontalScrollbarHeight()); + sizes.bottom = horizontal; + LayoutUnit vertical = LayoutUnit(layout_box->VerticalScrollbarWidth()); + if (layout_box->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) + sizes.left = vertical; + else + sizes.right = vertical; + return sizes.ConvertToLogical(style.GetWritingMode(), style.Direction()); +} bool NeedsInlineSizeToResolveLineLeft(const ComputedStyle& style, const ComputedStyle& container_style) { @@ -949,14 +987,30 @@ LayoutUnit LineOffsetForTextAlign(ETextAlign text_align, } } -LayoutUnit InlineOffsetForTextAlign(const ComputedStyle& container_style, - LayoutUnit space_left) { - TextDirection direction = container_style.Direction(); - LayoutUnit line_offset = LineOffsetForTextAlign( - container_style.GetTextAlign(), direction, space_left); - return IsLtr(direction) ? line_offset : space_left - line_offset; +namespace { + +// Calculates default content size for html and body elements in quirks mode. +// Returns |kIndefiniteSize| in all other cases. +LayoutUnit CalculateDefaultBlockSize( + const NGConstraintSpace& space, + const NGBlockNode& node, + const NGBoxStrut& border_scrollbar_padding) { + // In quirks mode, html and body elements will completely fill the ICB, block + // percentages should resolve against this size. + if (node.IsQuirkyAndFillsViewport()) { + LayoutUnit block_size = space.AvailableSize().block_size; + block_size -= ComputeMarginsForSelf(space, node.Style()).BlockSum(); + return std::max(block_size.ClampNegativeToZero(), + border_scrollbar_padding.BlockSum()); + } + return kIndefiniteSize; } +// Clamp the inline size of the scrollbar, unless it's larger than the inline +// size of the content box, in which case we'll return that instead. Scrollbar +// handling is quite bad in such situations, and this method here is just to +// make sure that left-hand scrollbars don't mess up scrollWidth. For the full +// story, visit http://crbug.com/724255. bool ClampScrollbarToContentBox(NGBoxStrut* scrollbars, LayoutUnit content_box_inline_size) { DCHECK(scrollbars->InlineSum()); @@ -972,49 +1026,64 @@ bool ClampScrollbarToContentBox(NGBoxStrut* scrollbars, return true; } -NGBoxStrut CalculateBorderScrollbarPadding( +} // namespace + +NGFragmentGeometry CalculateInitialFragmentGeometry( const NGConstraintSpace& constraint_space, - const NGBlockNode node) { - // If we are producing an anonymous fragment (e.g. a column), it has no - // borders, padding or scrollbars. Using the ones from the container can only - // cause trouble. - if (constraint_space.IsAnonymous()) - return NGBoxStrut(); - return ComputeBorders(constraint_space, node) + - ComputePadding(constraint_space, node.Style()) + - ComputeIntrinsicPadding(constraint_space, node) + - node.GetScrollbarSizes(); -} + const NGBlockNode& node) { + const ComputedStyle& style = node.Style(); + + NGBoxStrut border = ComputeBorders(constraint_space, node); + NGBoxStrut padding = ComputePadding(constraint_space, style); + NGBoxStrut scrollbar = ComputeScrollbars(constraint_space, node); + NGBoxStrut border_padding = border + padding; + NGBoxStrut border_scrollbar_padding = border_padding + scrollbar; -NGLogicalSize CalculateBorderBoxSize(const NGConstraintSpace& constraint_space, - const NGBlockNode& node, - const NGBoxStrut& border_padding, - LayoutUnit block_content_size) { // If we have a percentage size, we need to set the - // HasPercentHeightDescendants flag correctly so that flexboz knows it may + // HasPercentHeightDescendants flag correctly so that flexbox knows it may // need to redo layout and can also do some performance optimizations. - if (node.Style().LogicalHeight().IsPercentOrCalc() || - node.Style().LogicalMinHeight().IsPercentOrCalc() || - node.Style().LogicalMaxHeight().IsPercentOrCalc() || - (node.GetLayoutBox()->IsFlexItemIncludingNG() && - node.Style().FlexBasis().IsPercentOrCalc())) { + if (style.LogicalHeight().IsPercentOrCalc() || + style.LogicalMinHeight().IsPercentOrCalc() || + style.LogicalMaxHeight().IsPercentOrCalc() || + (node.IsFlexItem() && style.FlexBasis().IsPercentOrCalc())) { // This call has the side-effect of setting HasPercentHeightDescendants // correctly. node.GetLayoutBox()->ComputePercentageLogicalHeight(Length::Percent(0)); } - return NGLogicalSize( + LayoutUnit default_block_size = CalculateDefaultBlockSize( + constraint_space, node, border_scrollbar_padding); + LogicalSize border_box_size( ComputeInlineSizeForFragment(constraint_space, node, border_padding), - ComputeBlockSizeForFragment(constraint_space, node.Style(), - border_padding, block_content_size)); + ComputeBlockSizeForFragment(constraint_space, node, border_padding, + default_block_size)); + + if (UNLIKELY(border_box_size.inline_size < + border_scrollbar_padding.InlineSum() && + scrollbar.InlineSum() && !constraint_space.IsAnonymous())) { + ClampScrollbarToContentBox( + &scrollbar, border_box_size.inline_size - border_padding.InlineSum()); + } + + return {border_box_size, border, scrollbar, padding}; +} + +NGFragmentGeometry CalculateInitialMinMaxFragmentGeometry( + const NGConstraintSpace& constraint_space, + const NGBlockNode& node) { + NGBoxStrut border = ComputeBorders(constraint_space, node); + NGBoxStrut padding = ComputePadding(constraint_space, node.Style()); + NGBoxStrut scrollbar = ComputeScrollbars(constraint_space, node); + + return {/* border_box_size */ LogicalSize(), border, scrollbar, padding}; } -NGLogicalSize ShrinkAvailableSize(NGLogicalSize size, const NGBoxStrut& inset) { - DCHECK_NE(size.inline_size, NGSizeIndefinite); +LogicalSize ShrinkAvailableSize(LogicalSize size, const NGBoxStrut& inset) { + DCHECK_NE(size.inline_size, kIndefiniteSize); size.inline_size -= inset.InlineSum(); size.inline_size = std::max(size.inline_size, LayoutUnit()); - if (size.block_size != NGSizeIndefinite) { + if (size.block_size != kIndefiniteSize) { size.block_size -= inset.BlockSum(); size.block_size = std::max(size.block_size, LayoutUnit()); } @@ -1022,42 +1091,27 @@ NGLogicalSize ShrinkAvailableSize(NGLogicalSize size, const NGBoxStrut& inset) { return size; } -LayoutUnit CalculateDefaultBlockSize( - const NGConstraintSpace& space, - const NGBlockNode& node, - const NGBoxStrut& border_scrollbar_padding) { - // In quirks mode, html and body elements will completely fill the ICB, block - // percentages should resolve against this size. - if (node.IsQuirkyAndFillsViewport()) { - LayoutUnit block_size = space.AvailableSize().block_size; - block_size -= ComputeMarginsForSelf(space, node.Style()).BlockSum(); - return std::max(block_size.ClampNegativeToZero(), - border_scrollbar_padding.BlockSum()); - } - return NGSizeIndefinite; -} - namespace { // Implements the common part of the child percentage size calculation. Deals // with how percentages are propagated from parent to child in quirks mode. -NGLogicalSize AdjustChildPercentageSizeForQuirksAndFlex( +LogicalSize AdjustChildPercentageSizeForQuirksAndFlex( const NGConstraintSpace& space, const NGBlockNode node, - NGLogicalSize child_percentage_size, + LogicalSize child_percentage_size, LayoutUnit parent_percentage_block_size) { // Flex items may have a fixed block-size, but children shouldn't resolve // their percentages against this. if (space.IsFixedSizeBlock() && !space.FixedSizeBlockIsDefinite()) { DCHECK(node.IsFlexItem()); - child_percentage_size.block_size = NGSizeIndefinite; + child_percentage_size.block_size = kIndefiniteSize; return child_percentage_size; } // In quirks mode the percentage resolution height is passed from parent to // child. // https://quirks.spec.whatwg.org/#the-percentage-height-calculation-quirk - if (child_percentage_size.block_size == NGSizeIndefinite && + if (child_percentage_size.block_size == kIndefiniteSize && node.UseParentPercentageResolutionBlockSizeForChildren()) child_percentage_size.block_size = parent_percentage_block_size; @@ -1066,15 +1120,15 @@ NGLogicalSize AdjustChildPercentageSizeForQuirksAndFlex( } // namespace -NGLogicalSize CalculateChildPercentageSize( +LogicalSize CalculateChildPercentageSize( const NGConstraintSpace& space, const NGBlockNode node, - const NGLogicalSize& child_available_size) { + const LogicalSize& child_available_size) { // Anonymous block or spaces should pass the percent size straight through. if (space.IsAnonymous() || node.IsAnonymousBlock()) return space.PercentageResolutionSize(); - NGLogicalSize child_percentage_size = child_available_size; + LogicalSize child_percentage_size = child_available_size; bool is_table_cell_in_measure_phase = node.IsTableCell() && !space.IsFixedSizeBlock(); @@ -1082,7 +1136,7 @@ NGLogicalSize CalculateChildPercentageSize( // Table cells which are measuring their content, force their children to // have an indefinite percentage resolution size. if (is_table_cell_in_measure_phase) { - child_percentage_size.block_size = NGSizeIndefinite; + child_percentage_size.block_size = kIndefiniteSize; return child_percentage_size; } @@ -1098,10 +1152,10 @@ NGLogicalSize CalculateChildPercentageSize( space.PercentageResolutionBlockSize()); } -NGLogicalSize CalculateReplacedChildPercentageSize( +LogicalSize CalculateReplacedChildPercentageSize( const NGConstraintSpace& space, const NGBlockNode node, - NGLogicalSize border_box_size, + LogicalSize border_box_size, const NGBoxStrut& border_scrollbar_padding, const NGBoxStrut& border_padding) { // Anonymous block or spaces should pass the percent size straight through. @@ -1121,12 +1175,12 @@ NGLogicalSize CalculateReplacedChildPercentageSize( // To handle this we recalculate the border-box block-size, ignoring the // fixed size constraint. if (is_table_cell_in_layout_phase && has_resolvable_block_size) { - border_box_size.block_size = ComputeBlockSizeForFragmentInternal( - space, node.Style(), border_padding, - /* content_size */ NGSizeIndefinite); + border_box_size.block_size = + ComputeBlockSizeForFragmentInternal(space, node.Style(), border_padding, + /* content_size */ kIndefiniteSize); } - NGLogicalSize child_percentage_size = + LogicalSize child_percentage_size = ShrinkAvailableSize(border_box_size, border_scrollbar_padding); return AdjustChildPercentageSizeForQuirksAndFlex( @@ -1149,12 +1203,12 @@ LayoutUnit CalculateChildPercentageBlockSizeForMinMax( &parent_percentage_block_size); LayoutUnit child_percentage_block_size = - block_size == NGSizeIndefinite - ? NGSizeIndefinite + block_size == kIndefiniteSize + ? kIndefiniteSize : (block_size - border_padding.BlockSum()).ClampNegativeToZero(); // For OOF-positioned nodes, use the parent (containing-block) size. - if (child_percentage_block_size == NGSizeIndefinite && + if (child_percentage_block_size == kIndefiniteSize && (node.UseParentPercentageResolutionBlockSizeForChildren() || node.IsOutOfFlowPositioned())) child_percentage_block_size = parent_percentage_block_size; @@ -1162,4 +1216,49 @@ LayoutUnit CalculateChildPercentageBlockSizeForMinMax( return child_percentage_block_size; } +LayoutUnit ClampIntrinsicBlockSize(const NGBlockNode& node, + const NGBoxStrut& border_scrollbar_padding, + LayoutUnit current_intrinsic_block_size) { + // With contain: size, we ignore intrinsic sizing. If the block-size was + // specified as auto, its content-box size will become 0. + if (node.ShouldApplySizeContainment()) + return border_scrollbar_padding.BlockSum(); + + // If display locking induces size containment, then we replace its content + // size with the locked content size. + if (node.DisplayLockInducesSizeContainment()) { + return node.GetDisplayLockContext().GetLockedContentLogicalHeight() + + border_scrollbar_padding.BlockSum(); + } + + return current_intrinsic_block_size; +} + +base::Optional<MinMaxSize> CalculateMinMaxSizesIgnoringChildren( + const NGBlockNode& node, + const NGBoxStrut& border_scrollbar_padding, + NGMinMaxSizeType type) { + MinMaxSize sizes; + if (type == NGMinMaxSizeType::kBorderBoxSize) + sizes += border_scrollbar_padding.InlineSum(); + + // Size contained elements don't consider children for intrinsic sizing. + if (node.ShouldApplySizeContainment()) + return sizes; + + // Display locked elements override the content size, without considering + // children. Note that contain: size (above) takes precedence over display + // locking. + if (node.DisplayLockInducesSizeContainment()) { + sizes += node.GetDisplayLockContext().GetLockedContentLogicalWidth(); + return sizes; + } + + // If we don't have children, we can also determine the size immediately. + if (!node.FirstChild()) + return sizes; + + return base::nullopt; +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.h index 1d9be95fbf8..db3fcfa4b34 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.h @@ -7,11 +7,13 @@ #include "base/optional.h" #include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_size.h" #include "third_party/blink/renderer/core/layout/min_max_size.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" +#include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h" #include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/text/text_direction.h" @@ -30,13 +32,6 @@ class NGLayoutInputNode; // intrinsic sizes pass, and kLayout must be used during the layout pass. enum class LengthResolvePhase { kIntrinsic, kLayout }; -// LengthResolveType indicates what type length the function is being passed -// based on its CSS property. E.g. -// kMinSize - min-width / min-height -// kMaxSize - max-width / max-height -// kMainSize - width / height -enum class LengthResolveType { kMinSize, kMaxSize, kMainSize }; - inline bool NeedMinMaxSize(const ComputedStyle& style) { // This check is technically too broad (fill-available does not need intrinsic // size computation) but that's a rare case and only affects performance, not @@ -150,7 +145,8 @@ inline LayoutUnit ResolveMinBlockLength( LengthResolvePhase phase, const LayoutUnit* opt_percentage_resolution_block_size_for_min_max = nullptr) { - if (LIKELY(length.IsAuto() || + if (LIKELY(length.IsAuto() || length.IsMinContent() || + length.IsMaxContent() || length.IsFitContent() || BlockLengthUnresolvable( constraint_space, length, phase, opt_percentage_resolution_block_size_for_min_max))) @@ -171,7 +167,8 @@ inline LayoutUnit ResolveMaxBlockLength( LengthResolvePhase phase, const LayoutUnit* opt_percentage_resolution_block_size_for_min_max = nullptr) { - if (LIKELY(length.IsMaxSizeNone() || + if (LIKELY(length.IsMaxSizeNone() || length.IsMinContent() || + length.IsMaxContent() || length.IsFitContent() || BlockLengthUnresolvable( constraint_space, length, phase, opt_percentage_resolution_block_size_for_min_max))) @@ -256,15 +253,14 @@ CORE_EXPORT LayoutUnit ComputeInlineSizeForFragment( // Same as ComputeInlineSizeForFragment, but uses height instead of width. CORE_EXPORT LayoutUnit ComputeBlockSizeForFragment(const NGConstraintSpace&, - const ComputedStyle&, + const NGBlockNode&, const NGBoxStrut& border_padding, LayoutUnit content_size); // Computes intrinsic size for replaced elements. -CORE_EXPORT NGLogicalSize -ComputeReplacedSize(const NGLayoutInputNode&, - const NGConstraintSpace&, - const base::Optional<MinMaxSize>&); +CORE_EXPORT LogicalSize ComputeReplacedSize(const NGLayoutInputNode&, + const NGConstraintSpace&, + const base::Optional<MinMaxSize>&); // Based on available inline size, CSS computed column-width, CSS computed // column-count and CSS used column-gap, return CSS used column-count. @@ -384,6 +380,9 @@ inline NGLineBoxStrut ComputeLinePadding( style.IsFlippedLinesWritingMode()); } +CORE_EXPORT NGBoxStrut ComputeScrollbars(const NGConstraintSpace&, + const NGLayoutInputNode); + // Return true if we need to know the inline size of the fragment in order to // calculate its line-left offset. This is the case when we have auto margins, // or when block alignment isn't line-left (e.g. with align!=left, and always in @@ -411,62 +410,43 @@ CORE_EXPORT LayoutUnit LineOffsetForTextAlign(ETextAlign, TextDirection, LayoutUnit space_left); -// Same as |LineOffsetForTextAlign| but returns the logical inline offset -// instead of line-left offset. -CORE_EXPORT LayoutUnit InlineOffsetForTextAlign(const ComputedStyle&, - LayoutUnit space_left); - inline LayoutUnit ConstrainByMinMax(LayoutUnit length, LayoutUnit min, LayoutUnit max) { return std::max(min, std::min(length, max)); } -// Clamp the inline size of the scrollbar, unless it's larger than the inline -// size of the content box, in which case we'll return that instead. Scrollbar -// handling is quite bad in such situations, and this method here is just to -// make sure that left-hand scrollbars don't mess up scrollWidth. For the full -// story, visit http://crbug.com/724255. -bool ClampScrollbarToContentBox(NGBoxStrut* scrollbars, - LayoutUnit content_box_inline_size); +// Calculates the initial (pre-layout) fragment geometry given a node, and a +// constraint space. +// The "pre-layout" block-size may be indefinite, as we'll only have enough +// information to determine this post-layout. +CORE_EXPORT NGFragmentGeometry +CalculateInitialFragmentGeometry(const NGConstraintSpace&, const NGBlockNode&); -NGBoxStrut CalculateBorderScrollbarPadding( - const NGConstraintSpace& constraint_space, - const NGBlockNode node); - -// border_padding can be passed in as an optimization; otherwise this function -// will compute it itself. -NGLogicalSize CalculateBorderBoxSize( - const NGConstraintSpace& constraint_space, - const NGBlockNode& node, - const NGBoxStrut& border_padding, - LayoutUnit block_content_size = NGSizeIndefinite); +// Similar to |CalculateInitialFragmentGeometry| however will only calculate +// the border, scrollbar, and padding (resolving percentages to zero). +CORE_EXPORT NGFragmentGeometry +CalculateInitialMinMaxFragmentGeometry(const NGConstraintSpace&, + const NGBlockNode&); // Shrink and return the available size by an inset. This may e.g. be used to // convert from border-box to content-box size. Indefinite block size is // allowed, in which case the inset will be ignored for block size. -NGLogicalSize ShrinkAvailableSize(NGLogicalSize size, const NGBoxStrut& inset); - -// Calculates default content size for html and body elements in quirks mode. -// Returns NGSizeIndefinite in all other cases. -LayoutUnit CalculateDefaultBlockSize( - const NGConstraintSpace&, - const NGBlockNode&, - const NGBoxStrut& border_scrollbar_padding); +LogicalSize ShrinkAvailableSize(LogicalSize size, const NGBoxStrut& inset); // Calculates the percentage resolution size that children of the node should // use. -NGLogicalSize CalculateChildPercentageSize( +LogicalSize CalculateChildPercentageSize( const NGConstraintSpace&, const NGBlockNode node, - const NGLogicalSize& child_available_size); + const LogicalSize& child_available_size); // Calculates the percentage resolution size that replaced children of the node // should use. -NGLogicalSize CalculateReplacedChildPercentageSize( +LogicalSize CalculateReplacedChildPercentageSize( const NGConstraintSpace&, const NGBlockNode node, - NGLogicalSize border_box_size, + LogicalSize border_box_size, const NGBoxStrut& border_scrollbar_padding, const NGBoxStrut& border_padding); @@ -476,6 +456,22 @@ LayoutUnit CalculateChildPercentageBlockSizeForMinMax( const NGBoxStrut& border_padding, LayoutUnit parent_percentage_block_size); +// The following function clamps the calculated size based on the node +// requirements. Specifically, this adjusts the size based on size containment +// and display locking status. +LayoutUnit ClampIntrinsicBlockSize(const NGBlockNode&, + const NGBoxStrut& border_scrollbar_padding, + LayoutUnit current_intrinsic_block_size); + +// This function checks if the inline size of this node has to be calculated +// without considering children. If so, it returns the calculated size. +// Otherwise, it returns base::nullopt and the caller has to compute the size +// itself. +base::Optional<MinMaxSize> CalculateMinMaxSizesIgnoringChildren( + const NGBlockNode&, + const NGBoxStrut& border_scrollbar_padding, + NGMinMaxSizeType); + } // namespace blink #endif // NGLengthUtils_h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils_test.cc index 9790ddd8857..7028dfc5241 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils_test.cc @@ -24,7 +24,7 @@ static NGConstraintSpace ConstructConstraintSpace( bool fixed_inline = false, bool fixed_block = false, WritingMode writing_mode = WritingMode::kHorizontalTb) { - NGLogicalSize size = {LayoutUnit(inline_size), LayoutUnit(block_size)}; + LogicalSize size = {LayoutUnit(inline_size), LayoutUnit(block_size)}; return NGConstraintSpaceBuilder( writing_mode, writing_mode, @@ -86,16 +86,6 @@ class NGLengthUtilsTest : public testing::Test { LengthResolvePhase::kLayout); } - LayoutUnit ComputeBlockSizeForFragment( - NGConstraintSpace constraint_space = ConstructConstraintSpace(200, 300), - LayoutUnit content_size = LayoutUnit()) { - NGBoxStrut border_padding = ComputeBordersForTest(*style_) + - ComputePadding(constraint_space, *style_); - - return ::blink::ComputeBlockSizeForFragment(constraint_space, *style_, - border_padding, content_size); - } - scoped_refptr<ComputedStyle> style_; }; @@ -120,6 +110,20 @@ class NGLengthUtilsTestWithNode : public NGLayoutTest { border_padding, &sizes); } + LayoutUnit ComputeBlockSizeForFragment( + NGConstraintSpace constraint_space = ConstructConstraintSpace(200, 300), + LayoutUnit content_size = LayoutUnit()) { + LayoutBox* body = ToLayoutBox(GetDocument().body()->GetLayoutObject()); + body->SetStyle(style_); + body->SetPreferredLogicalWidthsDirty(); + NGBlockNode node(body); + + NGBoxStrut border_padding = ComputeBordersForTest(*style_) + + ComputePadding(constraint_space, *style_); + return ::blink::ComputeBlockSizeForFragment(constraint_space, node, + border_padding, content_size); + } + scoped_refptr<ComputedStyle> style_; }; @@ -344,7 +348,7 @@ TEST_F(NGLengthUtilsTestWithNode, testComputeInlineSizeForFragment) { ComputeInlineSizeForFragment(constraint_space, sizes)); } -TEST_F(NGLengthUtilsTest, testComputeBlockSizeForFragment) { +TEST_F(NGLengthUtilsTestWithNode, testComputeBlockSizeForFragment) { style_->SetLogicalHeight(Length::Percent(30)); EXPECT_EQ(LayoutUnit(90), ComputeBlockSizeForFragment()); @@ -402,11 +406,11 @@ TEST_F(NGLengthUtilsTest, testComputeBlockSizeForFragment) { // TODO(layout-ng): test {min,max}-content on max-height. } -TEST_F(NGLengthUtilsTest, testIndefinitePercentages) { +TEST_F(NGLengthUtilsTestWithNode, testIndefinitePercentages) { style_->SetMinHeight(Length::Fixed(20)); style_->SetHeight(Length::Percent(20)); - EXPECT_EQ(NGSizeIndefinite, + EXPECT_EQ(kIndefiniteSize, ComputeBlockSizeForFragment(ConstructConstraintSpace(200, -1), LayoutUnit(-1))); EXPECT_EQ(LayoutUnit(20), @@ -523,7 +527,7 @@ int GetUsedColumnWidth(int computed_column_count, int available_inline_size) { LayoutUnit column_width(computed_column_width); if (!computed_column_width) - column_width = LayoutUnit(NGSizeIndefinite); + column_width = LayoutUnit(kIndefiniteSize); return ResolveUsedColumnInlineSize(computed_column_count, column_width, LayoutUnit(used_column_gap), LayoutUnit(available_inline_size)) @@ -535,7 +539,7 @@ int GetUsedColumnCount(int computed_column_count, int available_inline_size) { LayoutUnit column_width(computed_column_width); if (!computed_column_width) - column_width = LayoutUnit(NGSizeIndefinite); + column_width = LayoutUnit(kIndefiniteSize); return ResolveUsedColumnCount(computed_column_count, column_width, LayoutUnit(used_column_gap), LayoutUnit(available_inline_size)); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_link.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_link.h index a67a326192c..5d78813eecf 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_link.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_link.h @@ -6,68 +6,38 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_LINK_H_ #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_offset.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" -#include "third_party/blink/renderer/platform/wtf/vector_traits.h" namespace blink { -// We use this struct to store NGLinks in a flexible array in fragments. We have -// to use this struct instead of using NGLink because flexible array members -// cannot have destructors, so we need to do manual refcounting. -struct NGLinkStorage { - NGPhysicalOffset Offset() const { return offset; } - const NGPhysicalFragment* get() const { return fragment; } - - operator bool() const { return fragment; } - const NGPhysicalFragment& operator*() const { return *fragment; } - const NGPhysicalFragment* operator->() const { return fragment; } - - const NGPhysicalFragment* fragment; - NGPhysicalOffset offset; -}; - // Class representing the offset of a child fragment relative to the // parent fragment. Fragments themselves have no position information // allowing entire fragment subtrees to be reused and cached regardless // of placement. -class CORE_EXPORT NGLink { - DISALLOW_NEW(); - - public: - NGLink() = default; - NGLink(scoped_refptr<const NGPhysicalFragment> fragment, - NGPhysicalOffset offset) - : fragment_(std::move(fragment)), offset_(offset) {} - NGLink(NGLink&& o) noexcept - : fragment_(std::move(o.fragment_)), offset_(o.offset_) {} - NGLink(const NGLinkStorage& storage) - : fragment_(storage.fragment), offset_(storage.offset) {} - ~NGLink() = default; - NGLink(const NGLink&) = default; - NGLink& operator=(const NGLink&) = default; - NGLink& operator=(NGLink&&) = default; - - // Returns the offset relative to the parent fragment's content-box. - NGPhysicalOffset Offset() const { return offset_; } +// This class is stored in a C-style regular array on +// NGPhysicalContainerFragment. It cannot have destructors. Fragment reference +// counting is done manually. +struct CORE_EXPORT NGLink { + PhysicalOffset Offset() const { return offset; } + const NGPhysicalFragment* get() const { return fragment; } - operator bool() const { return fragment_.get(); } - const NGPhysicalFragment& operator*() const { return *fragment_.get(); } - const NGPhysicalFragment* operator->() const { return fragment_.get(); } - const NGPhysicalFragment* get() const { return fragment_.get(); } + operator bool() const { return fragment; } + const NGPhysicalFragment& operator*() const { return *fragment; } + const NGPhysicalFragment* operator->() const { return fragment; } - private: - scoped_refptr<const NGPhysicalFragment> fragment_; - NGPhysicalOffset offset_; + // Returns a |NGLink| with newer generation if exists, or |this|. See + // |NGPhysicalFragment::PostLayout()| for more details. + const NGLink PostLayout() const { + if (const NGPhysicalFragment* new_fragment = fragment->PostLayout()) + return {new_fragment, offset}; + return *this; + } - // The builder classes needs to set the offset_ field during - // fragment construciton to allow the child vector to be moved - // instead of reconstructed during fragment construction. - friend class NGLayoutResult; + const NGPhysicalFragment* fragment; + PhysicalOffset offset; }; } // namespace blink -WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::NGLink) - #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_LINK_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc index 2da01999351..57d100f7344 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc @@ -7,6 +7,7 @@ #include "third_party/blink/renderer/core/layout/layout_block.h" #include "third_party/blink/renderer/core/layout/layout_box.h" #include "third_party/blink/renderer/core/layout/layout_flexible_box.h" +#include "third_party/blink/renderer/core/layout/layout_inline.h" #include "third_party/blink/renderer/core/layout/layout_object.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" @@ -19,6 +20,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_descendant.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" +#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/core/style/computed_style.h" namespace blink { @@ -36,7 +38,7 @@ bool IsAnonymousContainer(const LayoutObject* layout_object) { // - NG uses the anonymous' parent block, that contains all the anonymous // continuations. // This function finds the correct anonymous parent block. -LayoutBoxModelObject* GetOOFContainingBlockFromAnonymous( +const LayoutInline* GetOOFContainingBlockFromAnonymous( const LayoutObject* anonymous_block, EPosition child_position) { DCHECK(IsAnonymousContainer(anonymous_block)); @@ -59,8 +61,7 @@ LayoutBoxModelObject* GetOOFContainingBlockFromAnonymous( } DCHECK(absolute_containing_block->IsLayoutInline()); // Make absolute_containing_block continuation root. - return ToLayoutBoxModelObject( - absolute_containing_block->GetNode()->GetLayoutObject()); + return ToLayoutInline(absolute_containing_block->ContinuationRoot()); } } // namespace @@ -84,7 +85,7 @@ NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart( const NGConstraintSpace& container_space, const NGBoxStrut& border_scrollbar, NGBoxFragmentBuilder* container_builder, - base::Optional<NGLogicalSize> initial_containing_block_fixed_size) + base::Optional<LogicalSize> initial_containing_block_fixed_size) : container_builder_(container_builder), contains_absolute_(contains_absolute), contains_fixed_(contains_fixed) { @@ -101,13 +102,13 @@ NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart( ? *initial_containing_block_fixed_size : default_containing_block_.content_size_for_absolute; - default_containing_block_.container_offset = NGLogicalOffset( + default_containing_block_.container_offset = LogicalOffset( border_scrollbar.inline_start, border_scrollbar.block_start); NGPhysicalBoxStrut physical_border_scrollbar = border_scrollbar.ConvertToPhysical(container_style.GetWritingMode(), container_style.Direction()); - default_containing_block_.physical_container_offset = NGPhysicalOffset( + default_containing_block_.physical_container_offset = PhysicalOffset( physical_border_scrollbar.left, physical_border_scrollbar.top); } @@ -130,11 +131,11 @@ void NGOutOfFlowLayoutPart::Run(const LayoutBox* only_layout) { // See "Special case: oof css container" comment for detailed description. if (descendant_candidates.size() > 0 && current_container && !only_layout && IsAnonymousContainer(current_container)) { - LayoutBoxModelObject* absolute_containing_block = + const LayoutInline* absolute_containing_block = contains_absolute_ ? GetOOFContainingBlockFromAnonymous( current_container, EPosition::kAbsolute) : nullptr; - LayoutBoxModelObject* fixed_containing_block = + const LayoutInline* fixed_containing_block = contains_fixed_ ? GetOOFContainingBlockFromAnonymous(current_container, EPosition::kFixed) : nullptr; @@ -160,6 +161,8 @@ void NGOutOfFlowLayoutPart::Run(const LayoutBox* only_layout) { if (only_layout) return; + + wtf_size_t prev_placed_objects_size = placed_objects.size(); while (SweepLegacyDescendants(&placed_objects)) { container_builder_->GetAndClearOutOfFlowDescendantCandidates( &descendant_candidates, current_container); @@ -170,6 +173,19 @@ void NGOutOfFlowLayoutPart::Run(const LayoutBox* only_layout) { LayoutDescendantCandidates(&descendant_candidates, only_layout, &placed_objects); + + // Legacy currently has a bug where an OOF-positioned node is present + // within the current node's |LayoutBlock::PositionedObjects|, however it + // is not the containing-block for this node. + // + // This results in |LayoutDescendantCandidates| never performing layout on + // any additional objects. + wtf_size_t placed_objects_size = placed_objects.size(); + if (prev_placed_objects_size == placed_objects_size) { + NOTREACHED(); + break; + } + prev_placed_objects_size = placed_objects_size; } } @@ -219,14 +235,14 @@ bool NGOutOfFlowLayoutPart::SweepLegacyDescendants( LayoutBoxUtils::ComputeStaticPositionFromLegacy(*layout_box, container_builder_); - LayoutObject* css_container = layout_box->Container(); + const LayoutObject* css_container = layout_box->Container(); if (IsAnonymousContainer(css_container)) { css_container = GetOOFContainingBlockFromAnonymous( css_container, layout_box->Style()->GetPosition()); } container_builder_->AddOutOfFlowLegacyCandidate( NGBlockNode(layout_box), static_position, - css_container->IsBox() ? nullptr : css_container); + ToLayoutInlineOrNull(css_container)); } return true; } @@ -236,9 +252,8 @@ NGOutOfFlowLayoutPart::GetContainingBlockInfo( const NGOutOfFlowPositionedDescendant& descendant) const { if (descendant.inline_container) { const auto it = containing_blocks_map_.find(descendant.inline_container); - // TODO(atotic): Make this if-condition a DCHECK. - if (it != containing_blocks_map_.end()) - return it->value; + DCHECK(it != containing_blocks_map_.end()); + return it->value; } return default_containing_block_; } @@ -258,139 +273,128 @@ void NGOutOfFlowLayoutPart::ComputeInlineContainingBlocks( // Fetch start/end fragment info. container_builder_->ComputeInlineContainerFragments( &inline_container_fragments); - NGLogicalSize container_builder_size = container_builder_->Size(); - NGPhysicalSize container_builder_physical_size = - ToNGPhysicalSize(container_builder_size, - default_containing_block_.style->GetWritingMode()); + LogicalSize container_builder_size = container_builder_->Size(); + PhysicalSize container_builder_physical_size = + ToPhysicalSize(container_builder_size, + default_containing_block_.style->GetWritingMode()); // Translate start/end fragments into ContainingBlockInfo. for (auto& block_info : inline_container_fragments) { // Variables needed to describe ContainingBlockInfo const ComputedStyle* inline_cb_style = block_info.key->Style(); - NGLogicalSize inline_cb_size; - NGLogicalOffset container_offset; - NGPhysicalOffset physical_container_offset; - - if (!block_info.value.has_value()) { - // This happens when Legacy block is the default container. - // In this case, container builder does not have any fragments because - // ng layout algorithm did not run. - DCHECK(block_info.key->IsLayoutInline()); - NOTIMPLEMENTED() - << "Inline containing block might need geometry information"; - // TODO(atotic) ContainingBlockInfo geometry - // must be computed from Legacy algorithm - } else { - DCHECK(inline_cb_style); - NGBoxStrut inline_cb_borders = ComputeBordersForInline(*inline_cb_style); - - // The calculation below determines the size of the inline containing - // block rect. - // - // To perform this calculation we: - // 1. Determine the start_offset "^", this is at the logical-start (wrt. - // default containing block), of the start fragment rect. - // 2. Determine the end_offset "$", this is at the logical-end (wrt. - // default containing block), of the end fragment rect. - // 3. Determine the logical rectangle defined by these two offsets. - // - // Case 1a: Same direction, overlapping fragments. - // +--------------- - // ---> |^*****--------> - // +*----*--------- - // * * - // ------*----*+ - // ----> *****$| ---> - // ------------+ - // - // Case 1b: Different direction, overlapping fragments. - // +--------------- - // ---> ^******* <-----| - // *------*-------- - // * * - // -----*------* - // |<-- *******$ ---> - // ------------+ - // - // Case 2a: Same direction, non-overlapping fragments. - // +-------- - // ---------> |^ -----> - // +*------- - // * - // --------+ * - // ------->| $ ---> - // --------+ - // - // Case 2b: Same direction, non-overlapping fragments. - // +-------- - // ---------> ^ <-----| - // *-------- - // * - // --------+ * - // | <------ $ ---> - // --------+ - // - // Note in cases [1a, 2a] we need to account for the inline borders of - // the rectangles, where-as in [1b, 2b] we do not. This is handled by the - // is_same_direction check(s). - // - // Note in cases [2a, 2b] we don't allow a "negative" containing block - // size, we clamp negative sizes to zero. - WritingMode container_writing_mode = - default_containing_block_.style->GetWritingMode(); - TextDirection container_direction = - default_containing_block_.style->Direction(); - - bool is_same_direction = - container_direction == inline_cb_style->Direction(); - - // Step 1 - determine the start_offset. - const NGPhysicalOffsetRect& start_rect = - block_info.value->start_fragment_union_rect; - NGLogicalOffset start_offset = start_rect.offset.ConvertToLogical( - container_writing_mode, container_direction, - container_builder_physical_size, start_rect.size); - - // Make sure we add the inline borders, we don't need to do this in the - // inline direction if the blocks are in opposite directions. - start_offset.block_offset += inline_cb_borders.block_start; - if (is_same_direction) - start_offset.inline_offset += inline_cb_borders.inline_start; - - // Step 2 - determine the end_offset. - const NGPhysicalOffsetRect& end_rect = - block_info.value->end_fragment_union_rect; - NGLogicalOffset end_offset = end_rect.offset.ConvertToLogical( - container_writing_mode, container_direction, - container_builder_physical_size, end_rect.size); - - // Add in the size of the fragment to get the logical end of the fragment. - end_offset += end_rect.size.ConvertToLogical(container_writing_mode); - - // Make sure we substract the inline borders, we don't need to do this in - // the inline direction if the blocks are in opposite directions. - end_offset.block_offset -= inline_cb_borders.block_end; - if (is_same_direction) - end_offset.inline_offset -= inline_cb_borders.inline_end; - - // Make sure we don't end up with a rectangle with "negative" size. - end_offset.inline_offset = - std::max(end_offset.inline_offset, start_offset.inline_offset); - - // Step 3 - determine the logical rectange. - - // Determine the logical size of the containing block. - inline_cb_size = {end_offset.inline_offset - start_offset.inline_offset, - end_offset.block_offset - start_offset.block_offset}; - DCHECK_GE(inline_cb_size.inline_size, LayoutUnit()); - DCHECK_GE(inline_cb_size.block_size, LayoutUnit()); - - // Determine the container offsets. - container_offset = start_offset; - physical_container_offset = container_offset.ConvertToPhysical( - container_writing_mode, container_direction, - container_builder_physical_size, - ToNGPhysicalSize(inline_cb_size, container_writing_mode)); - } + LogicalSize inline_cb_size; + LogicalOffset container_offset; + PhysicalOffset physical_container_offset; + + DCHECK(block_info.value.has_value()); + DCHECK(inline_cb_style); + NGBoxStrut inline_cb_borders = ComputeBordersForInline(*inline_cb_style); + + // The calculation below determines the size of the inline containing block + // rect. + // + // To perform this calculation we: + // 1. Determine the start_offset "^", this is at the logical-start (wrt. + // default containing block), of the start fragment rect. + // 2. Determine the end_offset "$", this is at the logical-end (wrt. + // default containing block), of the end fragment rect. + // 3. Determine the logical rectangle defined by these two offsets. + // + // Case 1a: Same direction, overlapping fragments. + // +--------------- + // ---> |^*****--------> + // +*----*--------- + // * * + // ------*----*+ + // ----> *****$| ---> + // ------------+ + // + // Case 1b: Different direction, overlapping fragments. + // +--------------- + // ---> ^******* <-----| + // *------*-------- + // * * + // -----*------* + // |<-- *******$ ---> + // ------------+ + // + // Case 2a: Same direction, non-overlapping fragments. + // +-------- + // ---------> |^ -----> + // +*------- + // * + // --------+ * + // ------->| $ ---> + // --------+ + // + // Case 2b: Same direction, non-overlapping fragments. + // +-------- + // ---------> ^ <-----| + // *-------- + // * + // --------+ * + // | <------ $ ---> + // --------+ + // + // Note in cases [1a, 2a] we need to account for the inline borders of the + // rectangles, where-as in [1b, 2b] we do not. This is handled by the + // is_same_direction check(s). + // + // Note in cases [2a, 2b] we don't allow a "negative" containing block size, + // we clamp negative sizes to zero. + WritingMode container_writing_mode = + default_containing_block_.style->GetWritingMode(); + TextDirection container_direction = + default_containing_block_.style->Direction(); + + bool is_same_direction = + container_direction == inline_cb_style->Direction(); + + // Step 1 - determine the start_offset. + const PhysicalRect& start_rect = + block_info.value->start_fragment_union_rect; + LogicalOffset start_offset = start_rect.offset.ConvertToLogical( + container_writing_mode, container_direction, + container_builder_physical_size, start_rect.size); + + // Make sure we add the inline borders, we don't need to do this in the + // inline direction if the blocks are in opposite directions. + start_offset.block_offset += inline_cb_borders.block_start; + if (is_same_direction) + start_offset.inline_offset += inline_cb_borders.inline_start; + + // Step 2 - determine the end_offset. + const PhysicalRect& end_rect = block_info.value->end_fragment_union_rect; + LogicalOffset end_offset = end_rect.offset.ConvertToLogical( + container_writing_mode, container_direction, + container_builder_physical_size, end_rect.size); + + // Add in the size of the fragment to get the logical end of the fragment. + end_offset += end_rect.size.ConvertToLogical(container_writing_mode); + + // Make sure we subtract the inline borders, we don't need to do this in the + // inline direction if the blocks are in opposite directions. + end_offset.block_offset -= inline_cb_borders.block_end; + if (is_same_direction) + end_offset.inline_offset -= inline_cb_borders.inline_end; + + // Make sure we don't end up with a rectangle with "negative" size. + end_offset.inline_offset = + std::max(end_offset.inline_offset, start_offset.inline_offset); + + // Step 3 - determine the logical rectangle. + + // Determine the logical size of the containing block. + inline_cb_size = {end_offset.inline_offset - start_offset.inline_offset, + end_offset.block_offset - start_offset.block_offset}; + DCHECK_GE(inline_cb_size.inline_size, LayoutUnit()); + DCHECK_GE(inline_cb_size.block_size, LayoutUnit()); + + // Determine the container offsets. + container_offset = start_offset; + physical_container_offset = container_offset.ConvertToPhysical( + container_writing_mode, container_direction, + container_builder_physical_size, + ToPhysicalSize(inline_cb_size, container_writing_mode)); containing_blocks_map_.insert( block_info.key, ContainingBlockInfo{inline_cb_style, inline_cb_size, inline_cb_size, @@ -409,11 +413,12 @@ void NGOutOfFlowLayoutPart::LayoutDescendantCandidates( (!only_layout || candidate.node.GetLayoutBox() == only_layout)) { scoped_refptr<const NGLayoutResult> result = LayoutDescendant(candidate, only_layout); - container_builder_->AddChild(*result, - result->OutOfFlowPositionedOffset()); + container_builder_->AddChild(result->PhysicalFragment(), + result->OutOfFlowPositionedOffset(), + candidate.inline_container); placed_objects->insert(candidate.node.GetLayoutBox()); if (candidate.node.GetLayoutBox() != only_layout) - candidate.node.UseOldOutOfFlowPositioning(); + candidate.node.UseLegacyOutOfFlowPositioning(); } else { container_builder_->AddOutOfFlowDescendant(candidate); } @@ -447,18 +452,24 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutDescendant( WritingMode container_writing_mode = container_style.GetWritingMode(); WritingMode descendant_writing_mode = descendant_style.GetWritingMode(); - NGLogicalSize container_content_size = + LogicalSize container_content_size = container_info.ContentSize(descendant_style.GetPosition()); - NGLogicalSize container_content_size_in_child_writing_mode = - ToNGPhysicalSize(container_content_size, container_writing_mode) + LogicalSize container_content_size_in_child_writing_mode = + ToPhysicalSize(container_content_size, container_writing_mode) .ConvertToLogical(descendant_writing_mode); // Determine if we need to actually run the full OOF-positioned sizing, and // positioning algorithm. - if (scoped_refptr<const NGLayoutResult> cached_result = - node.CachedLayoutResultForOutOfFlowPositioned( - container_content_size_in_child_writing_mode)) - return cached_result; + // + // When this candidate has an inline container, the container may move without + // setting |NeedsLayout()| to the candidate and that there are cases where the + // cache validity cannot be determined. + if (!descendant.inline_container) { + if (scoped_refptr<const NGLayoutResult> cached_result = + node.CachedLayoutResultForOutOfFlowPositioned( + container_content_size_in_child_writing_mode)) + return cached_result; + } // Adjust the |static_position| (which is currently relative to the default // container's border-box). ng_absolute_utils expects the static position to @@ -474,6 +485,50 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutDescendant( .SetPercentageResolutionSize(container_content_size) .ToConstraintSpace(); + base::Optional<PaintLayerScrollableArea::FreezeScrollbarsScope> + freeze_scrollbars; + do { + scoped_refptr<const NGLayoutResult> layout_result = + Layout(node, descendant_constraint_space, static_position, + container_content_size, container_info, only_layout); + + if (!freeze_scrollbars.has_value()) { + // Since out-of-flow positioning sets up a constraint space with fixed + // inline-size, the regular layout code (|NGBlockNode::Layout()|) cannot + // re-layout if it discovers that a scrollbar was added or removed. Handle + // that situation here. The assumption is that if preferred logical widths + // are dirty after layout, AND its inline-size depends on preferred + // logical widths, it means that scrollbars appeared or disappeared. We + // have the same logic in legacy layout in + // |LayoutBlockFlow::UpdateBlockLayout()|. + if (node.GetLayoutBox()->PreferredLogicalWidthsDirty() && + AbsoluteNeedsChildInlineSize(descendant_style)) { + // Freeze the scrollbars for this layout pass. We don't want them to + // change *again*. + freeze_scrollbars.emplace(); + continue; + } + } + + return layout_result; + } while (true); +} + +scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( + NGBlockNode node, + const NGConstraintSpace& descendant_constraint_space, + const NGStaticPosition& static_position, + LogicalSize container_content_size, + const ContainingBlockInfo& container_info, + const LayoutBox* only_layout) { + const ComputedStyle& container_style = *container_info.style; + const ComputedStyle& descendant_style = node.Style(); + WritingMode container_writing_mode = container_style.GetWritingMode(); + WritingMode descendant_writing_mode = descendant_style.GetWritingMode(); + + LogicalSize container_content_size_in_child_writing_mode = + ToPhysicalSize(container_content_size, container_writing_mode) + .ConvertToLogical(descendant_writing_mode); NGBoxStrut border_padding = ComputeBorders(descendant_constraint_space, node) + ComputePadding(descendant_constraint_space, descendant_style); @@ -495,15 +550,15 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutDescendant( descendant_constraint_space, node, border_padding, input); } - base::Optional<NGLogicalSize> replaced_size; + base::Optional<LogicalSize> replaced_size; if (is_replaced) { replaced_size = ComputeReplacedSize(node, descendant_constraint_space, min_max_size); } else if (should_be_considered_as_replaced) { - replaced_size = NGLogicalSize{ + replaced_size = LogicalSize{ min_max_size->ShrinkToFit( descendant_constraint_space.AvailableSize().inline_size), - NGSizeIndefinite}; + kIndefiniteSize}; } NGAbsolutePhysicalPosition node_position = ComputePartialAbsoluteWithChildInlineSize( @@ -521,9 +576,8 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutDescendant( GenerateFragment(node, container_content_size_in_child_writing_mode, block_estimate, node_position); - DCHECK(layout_result->PhysicalFragment()); NGFragment fragment(descendant_writing_mode, - *layout_result->PhysicalFragment()); + layout_result->PhysicalFragment()); block_estimate = fragment.BlockSize(); } @@ -542,6 +596,8 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutDescendant( block_estimate, node_position); } + // TODO(mstensho): Move the rest of this method back into LayoutCandidate(). + if (node.GetLayoutBox()->IsLayoutNGObject()) { To<LayoutBlock>(node.GetLayoutBox()) ->SetIsLegacyInitiatedOutOfFlowLayout(false); @@ -558,12 +614,12 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutDescendant( // |inset| is relative to the container's padding-box. Convert this to being // relative to the default container's border-box. - NGLogicalOffset offset = container_info.container_offset; + LogicalOffset offset = container_info.container_offset; offset.inline_offset += inset.inline_start; offset.block_offset += inset.block_start; base::Optional<LayoutUnit> y = ComputeAbsoluteDialogYPosition( - *node.GetLayoutBox(), layout_result->PhysicalFragment()->Size().height); + *node.GetLayoutBox(), layout_result->PhysicalFragment().Size().height); if (y.has_value()) { if (IsHorizontalWritingMode(container_writing_mode)) offset.block_offset = *y; @@ -613,15 +669,15 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutDescendant( // the legacy offset from being wrt #container to being wrt #anonymous2. const LayoutObject* container = node.GetLayoutBox()->Container(); if (container->IsAnonymousBlock()) { - NGLogicalOffset container_offset = + LogicalOffset container_offset = container_builder_->GetChildOffset(container); offset -= container_offset; } else if (container->IsLayoutInline() && container->ContainingBlock()->IsAnonymousBlock()) { // Location of OOF with inline container, and anonymous containing block // is wrt container. - NGLogicalOffset container_offset = - container_builder_->GetChildOffset(container->Parent()); + LogicalOffset container_offset = + container_builder_->GetChildOffset(container->ContainingBlock()); offset -= container_offset; } @@ -655,7 +711,7 @@ bool NGOutOfFlowLayoutPart::IsContainingBlockForDescendant( // position calculation. scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::GenerateFragment( NGBlockNode descendant, - const NGLogicalSize& container_content_size_in_child_writing_mode, + const LogicalSize& container_content_size_in_child_writing_mode, const base::Optional<LayoutUnit>& block_estimate, const NGAbsolutePhysicalPosition& node_position) { // As the |block_estimate| is always in the descendant's writing mode, we @@ -668,7 +724,7 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::GenerateFragment( block_estimate ? *block_estimate : container_content_size_in_child_writing_mode.block_size; - NGLogicalSize available_size(inline_size, block_size); + LogicalSize available_size(inline_size, block_size); // TODO(atotic) will need to be adjusted for scrollbars. NGConstraintSpaceBuilder builder(writing_mode, writing_mode, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h index 0c04ae5c596..e414289366a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h @@ -8,7 +8,7 @@ #include "third_party/blink/renderer/core/core_export.h" #include "base/optional.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_offset.h" #include "third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h" #include "third_party/blink/renderer/core/style/computed_style_base_constants.h" #include "third_party/blink/renderer/platform/wtf/hash_map.h" @@ -51,7 +51,7 @@ class CORE_EXPORT NGOutOfFlowLayoutPart { const NGConstraintSpace& container_space, const NGBoxStrut& border_scrollbar, NGBoxFragmentBuilder* container_builder, - base::Optional<NGLogicalSize> initial_containing_block_fixed_size = + base::Optional<LogicalSize> initial_containing_block_fixed_size = base::nullopt); // Normally this function lays out and positions all out-of-flow objects from @@ -80,16 +80,16 @@ class CORE_EXPORT NGOutOfFlowLayoutPart { // Containing block style. const ComputedStyle* style; // Logical in containing block coordinates. - NGLogicalSize content_size_for_absolute; + LogicalSize content_size_for_absolute; // Content size for fixed is different for the ICB. - NGLogicalSize content_size_for_fixed; + LogicalSize content_size_for_fixed; // Offsets (both logical and physical) of the container's padding-box, wrt. // the default container's border-box. - NGLogicalOffset container_offset; - NGPhysicalOffset physical_container_offset; + LogicalOffset container_offset; + PhysicalOffset physical_container_offset; - NGLogicalSize ContentSize(EPosition position) const { + LogicalSize ContentSize(EPosition position) const { return position == EPosition::kAbsolute ? content_size_for_absolute : content_size_for_fixed; } @@ -112,12 +112,19 @@ class CORE_EXPORT NGOutOfFlowLayoutPart { const NGOutOfFlowPositionedDescendant&, const LayoutBox* only_layout); + scoped_refptr<const NGLayoutResult> Layout(NGBlockNode, + const NGConstraintSpace&, + const NGStaticPosition&, + LogicalSize container_content_size, + const ContainingBlockInfo&, + const LayoutBox* only_layout); + bool IsContainingBlockForDescendant( const NGOutOfFlowPositionedDescendant& descendant); scoped_refptr<const NGLayoutResult> GenerateFragment( NGBlockNode node, - const NGLogicalSize& container_content_size_in_child_writing_mode, + const LogicalSize& container_content_size_in_child_writing_mode, const base::Optional<LayoutUnit>& block_estimate, const NGAbsolutePhysicalPosition& node_position); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part_test.cc index a373b888b99..c950e7f86e4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part_test.cc @@ -61,7 +61,8 @@ TEST_F(NGOutOfFlowLayoutPartTest, FixedInsideAbs) { scoped_refptr<const NGLayoutResult> result = block_flow->GetCachedLayoutResult(); EXPECT_TRUE(result); - EXPECT_EQ(result->OutOfFlowPositionedDescendants().size(), (size_t)2); + EXPECT_EQ(result->PhysicalFragment().OutOfFlowPositionedDescendants().size(), + 2u); // Test the final result. Element* fixed_1 = GetDocument().getElementById("fixed1"); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_descendant.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_descendant.h index 69b0ff6c3fb..cab4bdc6ce5 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_descendant.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_descendant.h @@ -7,6 +7,7 @@ #include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/layout_inline.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_static_position.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" @@ -25,14 +26,19 @@ namespace blink { struct CORE_EXPORT NGOutOfFlowPositionedDescendant { NGBlockNode node; NGStaticPosition static_position; - const LayoutObject* inline_container; + // Continuation root of the optional inline container. + const LayoutInline* inline_container; + NGOutOfFlowPositionedDescendant( NGBlockNode node, NGStaticPosition static_position, - const LayoutObject* inline_container = nullptr) + const LayoutInline* inline_container = nullptr) : node(node), static_position(static_position), - inline_container(inline_container) {} + inline_container(inline_container) { + DCHECK(!inline_container || + inline_container == inline_container->ContinuationRoot()); + } }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_outline_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_outline_utils.cc index 3cdce969610..4e3ceb48b1e 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_outline_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_outline_utils.cc @@ -23,7 +23,7 @@ bool NGOutlineUtils::HasPaintedOutline(const ComputedStyle& style, bool NGOutlineUtils::IsInlineOutlineNonpaintingFragment( const NGPhysicalFragment& physical_fragment) { - LayoutObject* layout_object = physical_fragment.GetLayoutObject(); + const LayoutObject* layout_object = physical_fragment.GetLayoutObject(); if (!layout_object) return false; if (!layout_object->IsLayoutInline()) @@ -31,7 +31,7 @@ bool NGOutlineUtils::IsInlineOutlineNonpaintingFragment( if (layout_object->IsElementContinuation()) { // If continuation root did generate a fragment, // this fragment should not paint. - if (layout_object->GetNode()->GetLayoutObject()->FirstInlineFragment()) + if (layout_object->ContinuationRoot()->FirstInlineFragment()) return true; } if (!layout_object->FirstInlineFragment()) diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc index 107500346f3..bea1257ab78 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc @@ -16,63 +16,62 @@ namespace blink { -NGPageLayoutAlgorithm::NGPageLayoutAlgorithm(NGBlockNode node, - const NGConstraintSpace& space, - const NGBreakToken* break_token) - : NGLayoutAlgorithm(node, space, To<NGBlockBreakToken>(break_token)) { - container_builder_.SetIsNewFormattingContext(space.IsNewFormattingContext()); +NGPageLayoutAlgorithm::NGPageLayoutAlgorithm( + const NGLayoutAlgorithmParams& params) + : NGLayoutAlgorithm(params), + border_padding_(params.fragment_geometry.border + + params.fragment_geometry.padding), + border_scrollbar_padding_(border_padding_ + + params.fragment_geometry.scrollbar) { + container_builder_.SetIsNewFormattingContext( + params.space.IsNewFormattingContext()); + container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); } scoped_refptr<const NGLayoutResult> NGPageLayoutAlgorithm::Layout() { - NGBoxStrut borders = ComputeBorders(ConstraintSpace(), Node()); - NGBoxStrut scrollbars = Node().GetScrollbarSizes(); - NGBoxStrut padding = ComputePadding(ConstraintSpace(), Style()); - NGBoxStrut border_scrollbar_padding = borders + scrollbars + padding; - NGLogicalSize border_box_size = - CalculateBorderBoxSize(ConstraintSpace(), Node(), borders + padding); - NGLogicalSize content_box_size = - ShrinkAvailableSize(border_box_size, border_scrollbar_padding); - NGLogicalSize page_size = content_box_size; + LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); + LogicalSize content_box_size = + ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); + LogicalSize page_size = content_box_size; NGConstraintSpace child_space = CreateConstraintSpaceForPages(page_size); - container_builder_.SetInlineSize(border_box_size.inline_size); WritingMode writing_mode = ConstraintSpace().GetWritingMode(); scoped_refptr<const NGBlockBreakToken> break_token = BreakToken(); LayoutUnit intrinsic_block_size; - NGLogicalOffset page_offset(border_scrollbar_padding.StartOffset()); + LogicalOffset page_offset(border_scrollbar_padding_.StartOffset()); // TODO(mstensho): Handle auto block size. - NGLogicalOffset page_progression(LayoutUnit(), page_size.block_size); + LogicalOffset page_progression(LayoutUnit(), page_size.block_size); do { // Lay out one page. Each page will become a fragment. - NGBlockLayoutAlgorithm child_algorithm(Node(), child_space, - break_token.get()); + NGFragmentGeometry fragment_geometry = + CalculateInitialFragmentGeometry(child_space, Node()); + NGBlockLayoutAlgorithm child_algorithm( + {Node(), fragment_geometry, child_space, break_token.get()}); scoped_refptr<const NGLayoutResult> result = child_algorithm.Layout(); - const auto* page = To<NGPhysicalBoxFragment>(result->PhysicalFragment()); + const auto& page = result->PhysicalFragment(); - container_builder_.AddChild(*result, page_offset); + container_builder_.AddChild(page, page_offset); - NGBoxFragment logical_fragment(writing_mode, ConstraintSpace().Direction(), - *page); - intrinsic_block_size = - std::max(intrinsic_block_size, - page_offset.block_offset + logical_fragment.BlockSize()); + LayoutUnit page_block_size = NGFragment(writing_mode, page).BlockSize(); + intrinsic_block_size = std::max(intrinsic_block_size, + page_offset.block_offset + page_block_size); page_offset += page_progression; - break_token = To<NGBlockBreakToken>(page->BreakToken()); + break_token = To<NGBlockBreakToken>(page.BreakToken()); } while (break_token && !break_token->IsFinished()); container_builder_.SetIntrinsicBlockSize(intrinsic_block_size); // Recompute the block-axis size now that we know our content size. border_box_size.block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), borders + padding, intrinsic_block_size); + ConstraintSpace(), Node(), border_padding_, intrinsic_block_size); container_builder_.SetBlockSize(border_box_size.block_size); - container_builder_.SetBorders(ComputeBorders(ConstraintSpace(), Node())); - container_builder_.SetPadding(ComputePadding(ConstraintSpace(), Style())); - NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders + scrollbars, - &container_builder_) + NGOutOfFlowLayoutPart( + Node(), ConstraintSpace(), + container_builder_.Borders() + container_builder_.Scrollbar(), + &container_builder_) .Run(); // TODO(mstensho): Propagate baselines. @@ -82,12 +81,15 @@ scoped_refptr<const NGLayoutResult> NGPageLayoutAlgorithm::Layout() { base::Optional<MinMaxSize> NGPageLayoutAlgorithm::ComputeMinMaxSize( const MinMaxSizeInput& input) const { - NGBlockLayoutAlgorithm algorithm(Node(), ConstraintSpace()); + NGFragmentGeometry fragment_geometry = + CalculateInitialMinMaxFragmentGeometry(ConstraintSpace(), Node()); + NGBlockLayoutAlgorithm algorithm( + {Node(), fragment_geometry, ConstraintSpace()}); return algorithm.ComputeMinMaxSize(input); } NGConstraintSpace NGPageLayoutAlgorithm::CreateConstraintSpaceForPages( - const NGLogicalSize& page_size) const { + const LogicalSize& page_size) const { NGConstraintSpaceBuilder space_builder( ConstraintSpace(), Style().GetWritingMode(), /* is_new_fc */ true); space_builder.SetAvailableSize(page_size); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h index 62c49d87fb9..8491eaa198e 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h @@ -13,18 +13,15 @@ namespace blink { class NGBlockNode; class NGBlockBreakToken; -class NGBreakToken; class NGConstraintSpace; -struct NGLogicalSize; +struct LogicalSize; class CORE_EXPORT NGPageLayoutAlgorithm : public NGLayoutAlgorithm<NGBlockNode, NGBoxFragmentBuilder, NGBlockBreakToken> { public: - NGPageLayoutAlgorithm(NGBlockNode node, - const NGConstraintSpace& space, - const NGBreakToken* break_token = nullptr); + NGPageLayoutAlgorithm(const NGLayoutAlgorithmParams& params); scoped_refptr<const NGLayoutResult> Layout() override; @@ -33,7 +30,10 @@ class CORE_EXPORT NGPageLayoutAlgorithm private: NGConstraintSpace CreateConstraintSpaceForPages( - const NGLogicalSize& size) const; + const LogicalSize& size) const; + + NGBoxStrut border_padding_; + NGBoxStrut border_scrollbar_padding_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc index 08a62f35cd8..5fd401f8e02 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc @@ -22,33 +22,53 @@ namespace { struct SameSizeAsNGPhysicalBoxFragment : NGPhysicalContainerFragment { NGBaselineList baselines; - NGPhysicalBoxStrut box_struts[2]; + NGLink children[]; }; static_assert(sizeof(NGPhysicalBoxFragment) == sizeof(SameSizeAsNGPhysicalBoxFragment), "NGPhysicalBoxFragment should stay small"); +bool HasControlClip(const NGPhysicalBoxFragment& self) { + const LayoutBox* box = ToLayoutBoxOrNull(self.GetLayoutObject()); + return box && box->HasControlClip(); +} + +LayoutUnit BorderWidth(unsigned edges, unsigned edge, float border_width) { + return (edges & edge) ? LayoutUnit(border_width) : LayoutUnit(); +} + } // namespace scoped_refptr<const NGPhysicalBoxFragment> NGPhysicalBoxFragment::Create( NGBoxFragmentBuilder* builder, WritingMode block_or_line_writing_mode) { + const NGPhysicalBoxStrut borders = + builder->initial_fragment_geometry_->border.ConvertToPhysical( + builder->GetWritingMode(), builder->Direction()); + const NGPhysicalBoxStrut padding = + builder->initial_fragment_geometry_->padding.ConvertToPhysical( + builder->GetWritingMode(), builder->Direction()); + const size_t byte_size = sizeof(NGPhysicalBoxFragment) + + sizeof(NGLink) * builder->children_.size() + + (borders.IsZero() ? 0 : sizeof(borders)) + + (padding.IsZero() ? 0 : sizeof(padding)); // We store the children list inline in the fragment as a flexible // array. Therefore, we need to make sure to allocate enough space for // that array here, which requires a manual allocation + placement new. // The initialization of the array is done by NGPhysicalContainerFragment; // we pass the buffer as a constructor argument. void* data = ::WTF::Partitions::FastMalloc( - sizeof(NGPhysicalBoxFragment) + - builder->children_.size() * sizeof(NGLinkStorage), - ::WTF::GetStringWithTypeName<NGPhysicalBoxFragment>()); - new (data) NGPhysicalBoxFragment(builder, block_or_line_writing_mode); + byte_size, ::WTF::GetStringWithTypeName<NGPhysicalBoxFragment>()); + new (data) NGPhysicalBoxFragment(builder, borders, padding, + block_or_line_writing_mode); return base::AdoptRef(static_cast<NGPhysicalBoxFragment*>(data)); } NGPhysicalBoxFragment::NGPhysicalBoxFragment( NGBoxFragmentBuilder* builder, + const NGPhysicalBoxStrut& borders, + const NGPhysicalBoxStrut& padding, WritingMode block_or_line_writing_mode) : NGPhysicalContainerFragment( builder, @@ -58,56 +78,53 @@ NGPhysicalBoxFragment::NGPhysicalBoxFragment( ? kFragmentRenderedLegend : kFragmentBox, builder->BoxType()), - baselines_(builder->baselines_), - borders_(builder->borders_.ConvertToPhysical(builder->GetWritingMode(), - builder->Direction())), - padding_(builder->padding_.ConvertToPhysical(builder->GetWritingMode(), - builder->Direction())) { + baselines_(builder->baselines_) { DCHECK(GetLayoutObject() && GetLayoutObject()->IsBoxModelObject()); + has_borders_ = !borders.IsZero(); + if (has_borders_) + *const_cast<NGPhysicalBoxStrut*>(ComputeBordersAddress()) = borders; + has_padding_ = !padding.IsZero(); + if (has_padding_) + *const_cast<NGPhysicalBoxStrut*>(ComputePaddingAddress()) = padding; is_fieldset_container_ = builder->is_fieldset_container_; - is_old_layout_root_ = builder->is_old_layout_root_; + is_legacy_layout_root_ = builder->is_legacy_layout_root_; border_edge_ = builder->border_edges_.ToPhysical(builder->GetWritingMode()); children_inline_ = builder->layout_object_ && builder->layout_object_->ChildrenInline(); } bool NGPhysicalBoxFragment::HasSelfPaintingLayer() const { - return GetLayoutBoxModelObject().HasSelfPaintingLayer(); -} - -bool NGPhysicalBoxFragment::HasControlClip() const { - const LayoutObject* layout_object = GetLayoutObject(); - DCHECK(layout_object); - return layout_object->IsBox() && ToLayoutBox(layout_object)->HasControlClip(); + SECURITY_DCHECK(GetLayoutObject() && GetLayoutObject()->IsBoxModelObject()); + return (static_cast<const LayoutBoxModelObject*>(GetLayoutObject())) + ->HasSelfPaintingLayer(); } -LayoutRect NGPhysicalBoxFragment::OverflowClipRect( - const LayoutPoint& location, +PhysicalRect NGPhysicalBoxFragment::OverflowClipRect( + const PhysicalOffset& location, OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const { DCHECK(GetLayoutObject() && GetLayoutObject()->IsBox()); const LayoutBox* box = ToLayoutBox(GetLayoutObject()); return box->OverflowClipRect(location, overlay_scrollbar_clip_behavior); } -NGPhysicalOffsetRect NGPhysicalBoxFragment::ScrollableOverflow() const { +PhysicalRect NGPhysicalBoxFragment::ScrollableOverflow() const { DCHECK(GetLayoutObject()); - LayoutObject* layout_object = GetLayoutObject(); + const LayoutObject* layout_object = GetLayoutObject(); if (layout_object->IsBox()) { if (HasOverflowClip()) - return NGPhysicalOffsetRect({}, Size()); + return PhysicalRect({}, Size()); // Legacy is the source of truth for overflow - return NGPhysicalOffsetRect( - ToLayoutBox(layout_object)->LayoutOverflowRect()); + return PhysicalRect(ToLayoutBox(layout_object)->LayoutOverflowRect()); } else if (layout_object->IsLayoutInline()) { // Inline overflow is a union of child overflows. - NGPhysicalOffsetRect overflow({}, Size()); + PhysicalRect overflow({}, Size()); WritingMode container_writing_mode = Style().GetWritingMode(); TextDirection container_direction = Style().Direction(); for (const auto& child_fragment : Children()) { - NGPhysicalOffsetRect child_overflow = + PhysicalRect child_overflow = child_fragment->ScrollableOverflowForPropagation(layout_object); if (child_fragment->Style() != Style()) { - NGPhysicalOffset relative_offset = ComputeRelativeOffset( + PhysicalOffset relative_offset = ComputeRelativeOffset( child_fragment->Style(), container_writing_mode, container_direction, Size()); child_overflow.offset += relative_offset; @@ -119,7 +136,7 @@ NGPhysicalOffsetRect NGPhysicalBoxFragment::ScrollableOverflow() const { } else { NOTREACHED(); } - return NGPhysicalOffsetRect({}, Size()); + return PhysicalRect({}, Size()); } IntSize NGPhysicalBoxFragment::ScrolledContentOffset() const { @@ -128,16 +145,16 @@ IntSize NGPhysicalBoxFragment::ScrolledContentOffset() const { return box->ScrolledContentOffset(); } -LayoutSize NGPhysicalBoxFragment::ScrollSize() const { +PhysicalSize NGPhysicalBoxFragment::ScrollSize() const { DCHECK(GetLayoutObject() && GetLayoutObject()->IsBox()); const LayoutBox* box = ToLayoutBox(GetLayoutObject()); - return LayoutSize(box->ScrollWidth(), box->ScrollHeight()); + return {box->ScrollWidth(), box->ScrollHeight()}; } -NGPhysicalOffsetRect NGPhysicalBoxFragment::ComputeSelfInkOverflow() const { +PhysicalRect NGPhysicalBoxFragment::ComputeSelfInkOverflow() const { CheckCanUpdateInkOverflow(); const ComputedStyle& style = Style(); - LayoutRect ink_overflow({}, Size().ToLayoutSize()); + PhysicalRect ink_overflow({}, Size().ToLayoutSize()); DCHECK(GetLayoutObject()); if (style.HasVisualOverflowingEffect()) { @@ -145,22 +162,22 @@ NGPhysicalOffsetRect NGPhysicalBoxFragment::ComputeSelfInkOverflow() const { if (NGOutlineUtils::HasPaintedOutline(style, GetLayoutObject()->GetNode()) && !NGOutlineUtils::IsInlineOutlineNonpaintingFragment(*this)) { - Vector<LayoutRect> outline_rects; + Vector<PhysicalRect> outline_rects; // The result rects are in coordinates of this object's border box. AddSelfOutlineRects( - &outline_rects, LayoutPoint(), + &outline_rects, PhysicalOffset(), GetLayoutObject()->OutlineRectsShouldIncludeBlockVisualOverflow()); - LayoutRect rect = UnionRectEvenIfEmpty(outline_rects); - rect.Inflate(style.OutlineOutsetExtent()); + PhysicalRect rect = UnionRectEvenIfEmpty(outline_rects); + rect.Inflate(LayoutUnit(style.OutlineOutsetExtent())); ink_overflow.Unite(rect); } } - return NGPhysicalOffsetRect(ink_overflow); + return ink_overflow; } void NGPhysicalBoxFragment::AddSelfOutlineRects( - Vector<LayoutRect>* outline_rects, - const LayoutPoint& additional_offset, + Vector<PhysicalRect>* outline_rects, + const PhysicalOffset& additional_offset, NGOutlineType outline_type) const { // TODO(kojii): Needs inline_element_continuation logic from // LayoutBlockFlow::AddOutlineRects? @@ -168,17 +185,18 @@ void NGPhysicalBoxFragment::AddSelfOutlineRects( const LayoutObject* layout_object = GetLayoutObject(); DCHECK(layout_object); if (layout_object->IsLayoutInline()) { - Vector<LayoutRect> blockflow_outline_rects = - layout_object->PhysicalOutlineRects(LayoutPoint(), outline_type); + Vector<PhysicalRect> blockflow_outline_rects = + layout_object->OutlineRects(PhysicalOffset(), outline_type); // The rectangles returned are offset from the containing block. We need the // offset from this fragment. if (blockflow_outline_rects.size() > 0) { - LayoutPoint first_fragment_offset = blockflow_outline_rects[0].Location(); - LayoutSize corrected_offset = additional_offset - first_fragment_offset; + PhysicalOffset first_fragment_offset = blockflow_outline_rects[0].offset; + PhysicalOffset corrected_offset = + additional_offset - first_fragment_offset; for (auto& outline : blockflow_outline_rects) { - // Skip if both width and height are zero. Contaning blocks in empty + // Skip if both width and height are zero. Containing blocks in empty // linebox is one such case. - if (outline.Size().IsZero()) + if (outline.size.IsZero()) continue; outline.Move(corrected_offset); outline_rects->push_back(outline); @@ -192,16 +210,26 @@ void NGPhysicalBoxFragment::AddSelfOutlineRects( if (!layout_object->IsAnonymous()) { outline_rects->emplace_back(additional_offset, Size().ToLayoutSize()); } - if (outline_type == NGOutlineType::kIncludeBlockVisualOverflow && - !HasOverflowClip() && !HasControlClip()) { - AddOutlineRectsForNormalChildren(outline_rects, additional_offset, - outline_type); - - // TODO(kojii): LayoutBlock::AddOutlineRects handles positioned objects - // here. Do we need it? + !HasOverflowClip() && !HasControlClip(*this)) { + // Tricky code ahead: we pass a 0,0 additional_offset to + // AddOutlineRectsForNormalChildren, and add it in after the call. + // This is necessary because AddOutlineRectsForNormalChildren expects + // additional_offset to be an offset from containing_block. + // Since containing_block is our layout object, offset must be 0,0. + // https://crbug.com/968019 + Vector<PhysicalRect> children_rects; + AddOutlineRectsForNormalChildren(&children_rects, PhysicalOffset(), + outline_type, + ToLayoutBoxModelObject(GetLayoutObject())); + if (!additional_offset.IsZero()) { + for (auto& rect : children_rects) + rect.offset += additional_offset; + } + outline_rects->AppendVector(children_rects); + // LayoutBlock::AddOutlineRects also adds out of flow objects here. + // In LayoutNG out of flow objects are not part of the outline. } - // TODO(kojii): Needs inline_element_continuation logic from // LayoutBlockFlow::AddOutlineRects? } @@ -221,4 +249,58 @@ UBiDiLevel NGPhysicalBoxFragment::BidiLevel() const { return self_item->BidiLevel(); } +NGPixelSnappedPhysicalBoxStrut NGPhysicalBoxFragment::BorderWidths() const { + unsigned edges = BorderEdges(); + NGPhysicalBoxStrut box_strut( + BorderWidth(edges, NGBorderEdges::kTop, Style().BorderTopWidth()), + BorderWidth(edges, NGBorderEdges::kRight, Style().BorderRightWidth()), + BorderWidth(edges, NGBorderEdges::kBottom, Style().BorderBottomWidth()), + BorderWidth(edges, NGBorderEdges::kLeft, Style().BorderLeftWidth())); + return box_strut.SnapToDevicePixels(); +} + +#if DCHECK_IS_ON() +void NGPhysicalBoxFragment::CheckSameForSimplifiedLayout( + const NGPhysicalBoxFragment& other, + bool check_same_block_size) const { + DCHECK_EQ(layout_object_, other.layout_object_); + + LogicalSize size = size_.ConvertToLogical(Style().GetWritingMode()); + LogicalSize other_size = + other.size_.ConvertToLogical(Style().GetWritingMode()); + DCHECK_EQ(size.inline_size, other_size.inline_size); + if (check_same_block_size) + DCHECK_EQ(size.block_size, other_size.block_size); + + // "simplified" layout doesn't work within a fragmentation context. + DCHECK(!break_token_ && !other.break_token_); + + DCHECK_EQ(type_, other.type_); + DCHECK_EQ(sub_type_, other.sub_type_); + DCHECK_EQ(style_variant_, other.style_variant_); + + DCHECK_EQ(has_floating_descendants_, other.has_floating_descendants_); + DCHECK_EQ(has_orthogonal_flow_roots_, other.has_orthogonal_flow_roots_); + DCHECK_EQ(may_have_descendant_above_block_start_, + other.may_have_descendant_above_block_start_); + DCHECK_EQ(depends_on_percentage_block_size_, + other.depends_on_percentage_block_size_); + + DCHECK_EQ(children_inline_, other.children_inline_); + DCHECK_EQ(is_fieldset_container_, other.is_fieldset_container_); + DCHECK_EQ(is_legacy_layout_root_, other.is_legacy_layout_root_); + DCHECK_EQ(border_edge_, other.border_edge_); + + // The oof_positioned_descendants_ vector can change during "simplified" + // layout. This occurs when an OOF-descendant changes from "fixed" to + // "absolute" (or visa versa) changing its containing block. + + // Legacy layout can (incorrectly) shift baseline position(s) during + // "simplified" layout. + DCHECK(IsLegacyLayoutRoot() || baselines_ == other.baselines_); + DCHECK(Borders() == other.Borders()); + DCHECK(Padding() == other.Padding()); +} +#endif + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h index cd4ce4b02f9..ada1b07cd39 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h @@ -25,7 +25,7 @@ class CORE_EXPORT NGPhysicalBoxFragment final WritingMode block_or_line_writing_mode); ~NGPhysicalBoxFragment() { - for (const NGLinkStorage& child : Children()) + for (const NGLink& child : Children()) child.fragment->Release(); } @@ -33,56 +33,81 @@ class CORE_EXPORT NGPhysicalBoxFragment final return baselines_.Offset(request); } - const NGPhysicalBoxStrut Borders() const { return borders_; } + const NGPhysicalBoxStrut Borders() const { + if (!has_borders_) + return NGPhysicalBoxStrut(); + return *ComputeBordersAddress(); + } - const NGPhysicalBoxStrut Padding() const { return padding_; } + const NGPhysicalBoxStrut Padding() const { + if (!has_padding_) + return NGPhysicalBoxStrut(); + return *ComputePaddingAddress(); + } NGPixelSnappedPhysicalBoxStrut PixelSnappedPadding() const { - return padding_.SnapToDevicePixels(); + if (!has_padding_) + return NGPixelSnappedPhysicalBoxStrut(); + return ComputePaddingAddress()->SnapToDevicePixels(); } bool HasSelfPaintingLayer() const; bool ChildrenInline() const { return children_inline_; } - bool HasControlClip() const; - - NGPhysicalOffsetRect ScrollableOverflow() const; + PhysicalRect ScrollableOverflow() const; // TODO(layout-dev): These three methods delegate to legacy layout for now, // update them to use LayoutNG based overflow information from the fragment // and change them to use NG geometry types once LayoutNG supports overflow. - LayoutRect OverflowClipRect( - const LayoutPoint& location, + PhysicalRect OverflowClipRect( + const PhysicalOffset& location, OverlayScrollbarClipBehavior = kIgnorePlatformOverlayScrollbarSize) const; IntSize ScrolledContentOffset() const; - LayoutSize ScrollSize() const; + PhysicalSize ScrollSize() const; // Compute visual overflow of this box in the local coordinate. - NGPhysicalOffsetRect ComputeSelfInkOverflow() const; + PhysicalRect ComputeSelfInkOverflow() const; // Fragment offset is this fragment's offset from parent. // Needed to compensate for LayoutInline Legacy code offsets. - void AddSelfOutlineRects(Vector<LayoutRect>* outline_rects, - const LayoutPoint& additional_offset, + void AddSelfOutlineRects(Vector<PhysicalRect>* outline_rects, + const PhysicalOffset& additional_offset, NGOutlineType include_block_overflows) const; UBiDiLevel BidiLevel() const; - scoped_refptr<const NGPhysicalFragment> CloneWithoutOffset() const; + // Bitmask for border edges, see NGBorderEdges::Physical. + unsigned BorderEdges() const { return border_edge_; } + NGPixelSnappedPhysicalBoxStrut BorderWidths() const; - LayoutBoxModelObject& GetLayoutBoxModelObject() const { - SECURITY_DCHECK(GetLayoutObject() && GetLayoutObject()->IsBoxModelObject()); - return *static_cast<LayoutBoxModelObject*>(GetLayoutObject()); - } +#if DCHECK_IS_ON() + void CheckSameForSimplifiedLayout(const NGPhysicalBoxFragment&, + bool check_same_block_size) const; +#endif private: NGPhysicalBoxFragment(NGBoxFragmentBuilder* builder, + const NGPhysicalBoxStrut& borders, + const NGPhysicalBoxStrut& padding, WritingMode block_or_line_writing_mode); + const NGPhysicalBoxStrut* ComputeBordersAddress() const { + DCHECK(has_borders_); + return reinterpret_cast<const NGPhysicalBoxStrut*>(children_ + + Children().size()); + } + + const NGPhysicalBoxStrut* ComputePaddingAddress() const { + DCHECK(has_padding_); + const NGPhysicalBoxStrut* address = + reinterpret_cast<const NGPhysicalBoxStrut*>(children_ + + Children().size()); + return has_borders_ ? address + 1 : address; + } + NGBaselineList baselines_; - NGPhysicalBoxStrut borders_; - NGPhysicalBoxStrut padding_; - NGLinkStorage children_[]; + NGLink children_[]; + // borders and padding come from after |children_| if they are not zero. }; template <> diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment_test.cc index e135731ed46..aee364305bf 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment_test.cc @@ -89,35 +89,35 @@ TEST_F(NGPhysicalBoxFragmentTest, FloatingDescendantsInlineBlock) { // instead of using |contenteditable|. // Tests that a normal old layout root box fragment has correct box type. -TEST_F(NGPhysicalBoxFragmentTest, DISABLED_NormalOldLayoutRoot) { +TEST_F(NGPhysicalBoxFragmentTest, DISABLED_NormalLegacyLayoutRoot) { SetBodyInnerHTML("<div contenteditable>X</div>"); const NGPhysicalFragment* fragment = GetBodyFragment().Children().front().get(); ASSERT_TRUE(fragment); EXPECT_TRUE(fragment->IsBox()); EXPECT_EQ(NGPhysicalFragment::kNormalBox, fragment->BoxType()); - EXPECT_TRUE(fragment->IsOldLayoutRoot()); + EXPECT_TRUE(fragment->IsLegacyLayoutRoot()); EXPECT_TRUE(fragment->IsBlockFormattingContextRoot()); } // TODO(editing-dev): Once LayoutNG supports editing, we should change this // test to use LayoutNG tree. // Tests that a float old layout root box fragment has correct box type. -TEST_F(NGPhysicalBoxFragmentTest, DISABLED_FloatOldLayoutRoot) { +TEST_F(NGPhysicalBoxFragmentTest, DISABLED_FloatLegacyLayoutRoot) { SetBodyInnerHTML("<span contenteditable style='float:left'>X</span>foo"); const NGPhysicalFragment* fragment = GetBodyFragment().Children().front().get(); ASSERT_TRUE(fragment); EXPECT_TRUE(fragment->IsBox()); EXPECT_EQ(NGPhysicalFragment::kFloating, fragment->BoxType()); - EXPECT_TRUE(fragment->IsOldLayoutRoot()); + EXPECT_TRUE(fragment->IsLegacyLayoutRoot()); EXPECT_TRUE(fragment->IsBlockFormattingContextRoot()); } // TODO(editing-dev): Once LayoutNG supports editing, we should change this // test to use LayoutNG tree. // Tests that an inline block old layout root box fragment has correct box type. -TEST_F(NGPhysicalBoxFragmentTest, DISABLED_InlineBlockOldLayoutRoot) { +TEST_F(NGPhysicalBoxFragmentTest, DISABLED_InlineBlockLegacyLayoutRoot) { SetBodyInnerHTML( "<span contenteditable style='display:inline-block'>X</span>foo"); const auto* line_box = To<NGPhysicalContainerFragment>( @@ -126,7 +126,7 @@ TEST_F(NGPhysicalBoxFragmentTest, DISABLED_InlineBlockOldLayoutRoot) { ASSERT_TRUE(fragment); EXPECT_TRUE(fragment->IsBox()); EXPECT_EQ(NGPhysicalFragment::kAtomicInline, fragment->BoxType()); - EXPECT_TRUE(fragment->IsOldLayoutRoot()); + EXPECT_TRUE(fragment->IsLegacyLayoutRoot()); EXPECT_TRUE(fragment->IsBlockFormattingContextRoot()); } @@ -134,7 +134,8 @@ TEST_F(NGPhysicalBoxFragmentTest, DISABLED_InlineBlockOldLayoutRoot) { // test to use LayoutNG tree. // Tests that an out-of-flow positioned old layout root box fragment has correct // box type. -TEST_F(NGPhysicalBoxFragmentTest, DISABLED_OutOfFlowPositionedOldLayoutRoot) { +TEST_F(NGPhysicalBoxFragmentTest, + DISABLED_OutOfFlowPositionedLegacyLayoutRoot) { SetBodyInnerHTML( "<style>body {position: absolute}</style>" "<div contenteditable style='position: absolute'>X</div>"); @@ -143,7 +144,7 @@ TEST_F(NGPhysicalBoxFragmentTest, DISABLED_OutOfFlowPositionedOldLayoutRoot) { ASSERT_TRUE(fragment); EXPECT_TRUE(fragment->IsBox()); EXPECT_EQ(NGPhysicalFragment::kOutOfFlowPositioned, fragment->BoxType()); - EXPECT_TRUE(fragment->IsOldLayoutRoot()); + EXPECT_TRUE(fragment->IsLegacyLayoutRoot()); EXPECT_TRUE(fragment->IsBlockFormattingContextRoot()); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc index 23dd88be76f..16af3f47c2f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc @@ -17,6 +17,9 @@ namespace blink { namespace { struct SameSizeAsNGPhysicalContainerFragment : NGPhysicalFragment { + void* break_token; + std::unique_ptr<Vector<NGOutOfFlowPositionedDescendant>> + oof_positioned_descendants_; void* pointer; wtf_size_t size; }; @@ -30,34 +33,47 @@ static_assert(sizeof(NGPhysicalContainerFragment) == NGPhysicalContainerFragment::NGPhysicalContainerFragment( NGContainerFragmentBuilder* builder, WritingMode block_or_line_writing_mode, - NGLinkStorage* buffer, + NGLink* buffer, NGFragmentType type, unsigned sub_type) : NGPhysicalFragment(builder, type, sub_type), + break_token_(std::move(builder->break_token_)), + oof_positioned_descendants_( + builder->oof_positioned_descendants_.IsEmpty() + ? nullptr + : new Vector<NGOutOfFlowPositionedDescendant>( + std::move(builder->oof_positioned_descendants_))), buffer_(buffer), num_children_(builder->children_.size()) { - has_floating_descendants_ = builder->HasFloatingDescendants(); + has_floating_descendants_ = builder->has_floating_descendants_; + has_orthogonal_flow_roots_ = builder->has_orthogonal_flow_roots_; + may_have_descendant_above_block_start_ = + builder->may_have_descendant_above_block_start_; + depends_on_percentage_block_size_ = DependsOnPercentageBlockSize(*builder); - DCHECK_EQ(builder->children_.size(), builder->offsets_.size()); // Because flexible arrays need to be the last member in a class, we need to // have the buffer passed as a constructor argument and have the actual // storage be part of the subclass. wtf_size_t i = 0; for (auto& child : builder->children_) { - buffer[i].fragment = child.get(); + buffer[i].fragment = child.fragment.get(); buffer[i].fragment->AddRef(); - buffer[i].offset = builder->offsets_[i].ConvertToPhysical( + buffer[i].offset = child.offset.ConvertToPhysical( block_or_line_writing_mode, builder->Direction(), Size(), - child->Size()); + child.fragment->Size()); ++i; } } +NGPhysicalContainerFragment::~NGPhysicalContainerFragment() = default; + +// additional_offset must be offset from the containing_block. void NGPhysicalContainerFragment::AddOutlineRectsForNormalChildren( - Vector<LayoutRect>* outline_rects, - const LayoutPoint& additional_offset, - NGOutlineType outline_type) const { - for (const auto& child : Children()) { + Vector<PhysicalRect>* outline_rects, + const PhysicalOffset& additional_offset, + NGOutlineType outline_type, + const LayoutBoxModelObject* containing_block) const { + for (const auto& child : PostLayoutChildren()) { // Outlines of out-of-flow positioned descendants are handled in // NGPhysicalBoxFragment::AddSelfOutlineRects(). if (child->IsOutOfFlowPositioned()) @@ -66,54 +82,61 @@ void NGPhysicalContainerFragment::AddOutlineRectsForNormalChildren( // Outline of an element continuation or anonymous block continuation is // added when we iterate the continuation chain. // See NGPhysicalBoxFragment::AddSelfOutlineRects(). - if (LayoutObject* child_layout_object = child->GetLayoutObject()) { - auto* child_layout_block_flow = - DynamicTo<LayoutBlockFlow>(child_layout_object); - if (child_layout_object->IsElementContinuation() || - (child_layout_block_flow && - child_layout_block_flow->IsAnonymousBlockContinuation())) - continue; + if (!child->IsLineBox()) { + const LayoutObject* child_layout_object = child->GetLayoutObject(); + if (auto* child_layout_block_flow = + DynamicTo<LayoutBlockFlow>(child_layout_object)) { + if (child_layout_object->IsElementContinuation() || + child_layout_block_flow->IsAnonymousBlockContinuation()) + continue; + } } - AddOutlineRectsForDescendant(child, outline_rects, additional_offset, - outline_type); + outline_type, containing_block); } } +// additional_offset must be offset from the containing_block because +// LocalToAncestorRect returns rects wrt containing_block. void NGPhysicalContainerFragment::AddOutlineRectsForDescendant( const NGLink& descendant, - Vector<LayoutRect>* outline_rects, - const LayoutPoint& additional_offset, - NGOutlineType outline_type) const { + Vector<PhysicalRect>* outline_rects, + const PhysicalOffset& additional_offset, + NGOutlineType outline_type, + const LayoutBoxModelObject* containing_block) const { if (descendant->IsText() || descendant->IsListMarker()) return; if (const auto* descendant_box = DynamicTo<NGPhysicalBoxFragment>(descendant.get())) { - LayoutObject* descendant_layout_object = descendant_box->GetLayoutObject(); + const LayoutObject* descendant_layout_object = + descendant_box->GetLayoutObject(); DCHECK(descendant_layout_object); + // TODO(layoutng): Explain this check. I assume we need it because layers + // may have transforms and so we have to go through LocalToAncestorRects? if (descendant_box->HasLayer()) { - Vector<LayoutRect> layer_outline_rects; - descendant_box->AddSelfOutlineRects(&layer_outline_rects, LayoutPoint(), - outline_type); + Vector<PhysicalRect> layer_outline_rects; + descendant_box->AddSelfOutlineRects(&layer_outline_rects, + PhysicalOffset(), outline_type); + + // Don't pass additional_offset because LocalToAncestorRects will itself + // apply it. descendant_layout_object->LocalToAncestorRects( - layer_outline_rects, ToLayoutBoxModelObject(GetLayoutObject()), - LayoutPoint(), additional_offset); + layer_outline_rects, containing_block, PhysicalOffset(), + PhysicalOffset()); outline_rects->AppendVector(layer_outline_rects); return; } if (descendant_layout_object->IsBox()) { descendant_box->AddSelfOutlineRects( - outline_rects, - additional_offset + descendant.Offset().ToLayoutPoint(), - outline_type); + outline_rects, additional_offset + descendant.Offset(), outline_type); return; } DCHECK(descendant_layout_object->IsLayoutInline()); - LayoutInline* descendant_layout_inline = + const LayoutInline* descendant_layout_inline = ToLayoutInline(descendant_layout_object); // As an optimization, an ancestor has added rects for its line boxes // covering descendants' line boxes, so descendants don't need to add line @@ -131,8 +154,8 @@ void NGPhysicalContainerFragment::AddOutlineRectsForDescendant( if (const auto* descendant_line_box = DynamicTo<NGPhysicalLineBoxFragment>(descendant.get())) { descendant_line_box->AddOutlineRectsForNormalChildren( - outline_rects, additional_offset + descendant.Offset().ToLayoutPoint(), - outline_type); + outline_rects, additional_offset + descendant.Offset(), outline_type, + containing_block); if (!descendant_line_box->Size().IsEmpty()) { outline_rects->emplace_back(additional_offset, @@ -155,4 +178,42 @@ void NGPhysicalContainerFragment::AddOutlineRectsForDescendant( } } +bool NGPhysicalContainerFragment::DependsOnPercentageBlockSize( + const NGContainerFragmentBuilder& builder) { + NGLayoutInputNode node = builder.node_; + + if (!node || node.IsInline()) + return builder.has_descendant_that_depends_on_percentage_block_size_; + + // NOTE: If an element is OOF positioned, and has top/bottom constraints + // which are percentage based, this function will return false. + // + // This is fine as the top/bottom constraints are computed *before* layout, + // and the result is set as a fixed-block-size constraint. (And the caching + // logic will never check the result of this function). + // + // The result of this function still may be used for an OOF positioned + // element if it has a percentage block-size however, but this will return + // the correct result from below. + + if ((builder.has_descendant_that_depends_on_percentage_block_size_ || + builder.is_legacy_layout_root_) && + node.UseParentPercentageResolutionBlockSizeForChildren()) { + // Quirks mode has different %-block-size behaviour, than standards mode. + // An arbitrary descendant may depend on the percentage resolution + // block-size given. + // If this is also an anonymous block we need to mark ourselves dependent + // if we have a dependent child. + return true; + } + + const ComputedStyle& style = builder.Style(); + if (style.LogicalHeight().IsPercentOrCalc() || + style.LogicalMinHeight().IsPercentOrCalc() || + style.LogicalMaxHeight().IsPercentOrCalc()) + return true; + + return false; +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h index 3da2322035a..8b5c9f878f6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h @@ -5,8 +5,10 @@ #ifndef NGPhysicalContainerFragment_h #define NGPhysicalContainerFragment_h +#include "base/containers/span.h" #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" +#include "third_party/blink/renderer/core/layout/ng/ng_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_link.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" #include "third_party/blink/renderer/platform/wtf/casting.h" @@ -15,57 +17,166 @@ namespace blink { class NGContainerFragmentBuilder; +struct NGOutOfFlowPositionedDescendant; enum class NGOutlineType; class CORE_EXPORT NGPhysicalContainerFragment : public NGPhysicalFragment { public: - class ChildLinkList { + class ChildLinkListBase { public: - ChildLinkList(wtf_size_t count, const NGLinkStorage* buffer) + ChildLinkListBase(wtf_size_t count, const NGLink* buffer) : count_(count), buffer_(buffer) {} wtf_size_t size() const { return count_; } - const NGLinkStorage& operator[](wtf_size_t idx) const { - return buffer_[idx]; - } - const NGLinkStorage& front() const { return buffer_[0]; } - const NGLinkStorage& back() const { return buffer_[count_ - 1]; } - - const NGLinkStorage* begin() const { return buffer_; } - const NGLinkStorage* end() const { return begin() + count_; } - bool IsEmpty() const { return count_ == 0; } - private: + protected: wtf_size_t count_; - const NGLinkStorage* buffer_; + const NGLink* buffer_; + }; + + class ChildLinkList : public ChildLinkListBase { + public: + using ChildLinkListBase::ChildLinkListBase; + + const NGLink& operator[](wtf_size_t idx) const { return buffer_[idx]; } + const NGLink& front() const { return buffer_[0]; } + const NGLink& back() const { return buffer_[count_ - 1]; } + + const NGLink* begin() const { return buffer_; } + const NGLink* end() const { return begin() + count_; } }; + // Same as |ChildLinkList|, except that each |NGLink| has the latest + // generation of post-layout. See |NGPhysicalFragment::UpdatedFragment()| for + // more details. + class PostLayoutChildLinkList : public ChildLinkListBase { + public: + using ChildLinkListBase::ChildLinkListBase; + + class ConstIterator { + STACK_ALLOCATED(); + + public: + ConstIterator(const NGLink* current) : current_(current) {} + + const NGLink& operator*() const { return *PostLayoutOrCurrent(); } + const NGLink* operator->() const { return PostLayoutOrCurrent(); } + + ConstIterator& operator++() { + ++current_; + return *this; + } + bool operator==(const ConstIterator& other) const { + return current_ == other.current_; + } + bool operator!=(const ConstIterator& other) const { + return current_ != other.current_; + } + + private: + const NGLink* PostLayoutOrCurrent() const { + post_layout_.fragment = current_->fragment->PostLayout(); + if (!post_layout_.fragment) + return current_; + post_layout_.offset = current_->offset; + return &post_layout_; + } + + const NGLink* current_; + mutable NGLink post_layout_; + }; + using const_iterator = ConstIterator; + + const_iterator begin() const { return const_iterator(buffer_); } + const_iterator end() const { return const_iterator(buffer_ + count_); } + + const NGLink operator[](wtf_size_t idx) const { + CHECK_LT(idx, count_); + return buffer_[idx].PostLayout(); + } + const NGLink front() const { return (*this)[0]; } + const NGLink back() const { return (*this)[count_ - 1]; } + }; + + ~NGPhysicalContainerFragment(); + + NGBreakToken* BreakToken() const { return break_token_.get(); } + + // Returns the children of |this|. + // + // Note, children in this collection maybe old generations. Items in this + // collection are safe, but their children (grandchildren of |this|) maybe + // from deleted nodes or LayoutObjects. Also see |PostLayoutChildren()|. ChildLinkList Children() const { return ChildLinkList(num_children_, buffer_); } - void AddOutlineRectsForNormalChildren(Vector<LayoutRect>* outline_rects, - const LayoutPoint& additional_offset, - NGOutlineType outline_type) const; - void AddOutlineRectsForDescendant(const NGLink& descendant, - Vector<LayoutRect>* rects, - const LayoutPoint& additional_offset, - NGOutlineType outline_type) const; + // Similar to |Children()| but all children are the latest generation of + // post-layout, and therefore all descendants are safe. + PostLayoutChildLinkList PostLayoutChildren() const { + return PostLayoutChildLinkList(num_children_, buffer_); + } bool HasFloatingDescendants() const { return has_floating_descendants_; } + bool HasOrthogonalFlowRoots() const { return has_orthogonal_flow_roots_; } + + // Returns true if we have a descendant within this formatting context, which + // is potentially above our block-start edge. + bool MayHaveDescendantAboveBlockStart() const { + return may_have_descendant_above_block_start_; + } + + // Returns true if we aren't able to re-use this fragment if the + // |NGConstraintSpace::PercentageResolutionBlockSize| changes. + bool DependsOnPercentageBlockSize() const { + return depends_on_percentage_block_size_; + } + + bool HasOutOfFlowPositionedDescendants() const { + DCHECK(!oof_positioned_descendants_ || + !oof_positioned_descendants_->IsEmpty()); + return oof_positioned_descendants_.get(); + } + + base::span<NGOutOfFlowPositionedDescendant> OutOfFlowPositionedDescendants() + const { + if (!HasOutOfFlowPositionedDescendants()) + return base::span<NGOutOfFlowPositionedDescendant>(); + return {oof_positioned_descendants_->data(), + oof_positioned_descendants_->size()}; + } + protected: // block_or_line_writing_mode is used for converting the child offsets. NGPhysicalContainerFragment(NGContainerFragmentBuilder*, WritingMode block_or_line_writing_mode, - NGLinkStorage* buffer, + NGLink* buffer, NGFragmentType, unsigned sub_type); + void AddOutlineRectsForNormalChildren( + Vector<PhysicalRect>* outline_rects, + const PhysicalOffset& additional_offset, + NGOutlineType outline_type, + const LayoutBoxModelObject* containing_block) const; + void AddOutlineRectsForDescendant( + const NGLink& descendant, + Vector<PhysicalRect>* rects, + const PhysicalOffset& additional_offset, + NGOutlineType outline_type, + const LayoutBoxModelObject* containing_block) const; + + static bool DependsOnPercentageBlockSize(const NGContainerFragmentBuilder&); + + scoped_refptr<NGBreakToken> break_token_; + const std::unique_ptr<Vector<NGOutOfFlowPositionedDescendant>> + oof_positioned_descendants_; + // Because flexible arrays need to be the last member in a class, the actual // storage is in the subclass and we just keep a pointer to it here. - const NGLinkStorage* buffer_; + const NGLink* buffer_; wtf_size_t num_children_; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc index 456798a7648..b81af6f690c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc @@ -12,7 +12,6 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" -#include "third_party/blink/renderer/core/layout/ng/ng_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/style/computed_style.h" @@ -24,8 +23,8 @@ namespace { struct SameSizeAsNGPhysicalFragment : RefCounted<const NGPhysicalFragment, NGPhysicalFragmentTraits> { - void* pointers[2]; - NGPhysicalSize size; + void* layout_object; + PhysicalSize size; unsigned flags; }; @@ -33,12 +32,11 @@ static_assert(sizeof(NGPhysicalFragment) == sizeof(SameSizeAsNGPhysicalFragment), "NGPhysicalFragment should stay small"); -bool AppendFragmentOffsetAndSize( - const NGPhysicalFragment* fragment, - base::Optional<NGPhysicalOffset> fragment_offset, - StringBuilder* builder, - NGPhysicalFragment::DumpFlags flags, - bool has_content) { +bool AppendFragmentOffsetAndSize(const NGPhysicalFragment* fragment, + base::Optional<PhysicalOffset> fragment_offset, + StringBuilder* builder, + NGPhysicalFragment::DumpFlags flags, + bool has_content) { if (flags & NGPhysicalFragment::DumpOffset) { if (has_content) builder->Append(" "); @@ -83,10 +81,10 @@ String StringForBoxType(const NGPhysicalFragment& fragment) { result.Append("block-flow-root"); break; } - if (fragment.IsOldLayoutRoot()) { + if (fragment.IsLegacyLayoutRoot()) { if (result.length()) result.Append(" "); - result.Append("old-layout-root"); + result.Append("legacy-layout-root"); } if (fragment.IsBlockFlow()) { if (result.length()) @@ -114,7 +112,7 @@ String StringForBoxType(const NGPhysicalFragment& fragment) { } void AppendFragmentToString(const NGPhysicalFragment* fragment, - base::Optional<NGPhysicalOffset> fragment_offset, + base::Optional<PhysicalOffset> fragment_offset, StringBuilder* builder, NGPhysicalFragment::DumpFlags flags, unsigned indent = 2) { @@ -210,10 +208,6 @@ void AppendFragmentToString(const NGPhysicalFragment* fragment, builder->Append("\n"); } -LayoutUnit BorderWidth(unsigned edges, unsigned edge, float border_width) { - return (edges & edge) ? LayoutUnit(border_width) : LayoutUnit(); -} - } // namespace // static @@ -224,31 +218,32 @@ void NGPhysicalFragmentTraits::Destruct(const NGPhysicalFragment* fragment) { NGPhysicalFragment::NGPhysicalFragment(NGFragmentBuilder* builder, NGFragmentType type, unsigned sub_type) - : layout_object_(builder->layout_object_), - size_(ToNGPhysicalSize(builder->size_, builder->GetWritingMode())), - break_token_(std::move(builder->break_token_)), + : layout_object_(*builder->layout_object_), + size_(ToPhysicalSize(builder->size_, builder->GetWritingMode())), type_(type), sub_type_(sub_type), style_variant_((unsigned)builder->style_variant_), has_floating_descendants_(false), is_fieldset_container_(false), - is_old_layout_root_(false) {} + is_legacy_layout_root_(false) { + DCHECK(builder->layout_object_); +} NGPhysicalFragment::NGPhysicalFragment(LayoutObject* layout_object, NGStyleVariant style_variant, - NGPhysicalSize size, + PhysicalSize size, NGFragmentType type, - unsigned sub_type, - scoped_refptr<NGBreakToken> break_token) - : layout_object_(layout_object), + unsigned sub_type) + : layout_object_(*layout_object), size_(size), - break_token_(std::move(break_token)), type_(type), sub_type_(sub_type), style_variant_((unsigned)style_variant), has_floating_descendants_(false), is_fieldset_container_(false), - is_old_layout_root_(false) {} + is_legacy_layout_root_(false) { + DCHECK(layout_object); +} // Keep the implementation of the destructor here, to avoid dependencies on // ComputedStyle in the header file. @@ -272,29 +267,34 @@ void NGPhysicalFragment::Destroy() const { } } -const ComputedStyle& NGPhysicalFragment::Style() const { - if (auto* line_box = DynamicTo<NGPhysicalLineBoxFragment>(this)) - return line_box->Style(); +const ComputedStyle& NGPhysicalFragment::SlowEffectiveStyle() const { switch (StyleVariant()) { case NGStyleVariant::kStandard: - DCHECK(GetLayoutObject()); - return *GetLayoutObject()->Style(); + return layout_object_.StyleRef(); case NGStyleVariant::kFirstLine: - DCHECK(GetLayoutObject()); - return *GetLayoutObject()->FirstLineStyle(); + return layout_object_.FirstLineStyleRef(); case NGStyleVariant::kEllipsis: - return To<NGPhysicalTextFragment>(this)->Style(); + DCHECK_EQ(Type(), kFragmentText); + DCHECK_EQ(StyleVariant(), NGStyleVariant::kEllipsis); + // The ellipsis is styled according to the line style. + // https://drafts.csswg.org/css-ui/#ellipsing-details + // Use first-line style if exists since most cases it is the first line. + // TODO(kojii): Should determine if it's really in the first line. + DCHECK(layout_object_.IsInline()); + if (LayoutObject* block = layout_object_.ContainingBlock()) + return block->FirstLineStyleRef(); + return layout_object_.FirstLineStyleRef(); } NOTREACHED(); - return *GetLayoutObject()->Style(); + return layout_object_.StyleRef(); } Node* NGPhysicalFragment::GetNode() const { - return layout_object_ ? layout_object_->GetNode() : nullptr; + return !IsLineBox() ? layout_object_.GetNode() : nullptr; } bool NGPhysicalFragment::HasLayer() const { - return layout_object_ && layout_object_->HasLayer(); + return !IsLineBox() && layout_object_.HasLayer(); } PaintLayer* NGPhysicalFragment::Layer() const { @@ -303,7 +303,7 @@ PaintLayer* NGPhysicalFragment::Layer() const { // If the underlying LayoutObject has a layer it's guaranteed to be a // LayoutBoxModelObject. - return static_cast<LayoutBoxModelObject*>(layout_object_)->Layer(); + return static_cast<LayoutBoxModelObject&>(layout_object_).Layer(); } bool NGPhysicalFragment::HasSelfPaintingLayer() const { @@ -312,45 +312,48 @@ bool NGPhysicalFragment::HasSelfPaintingLayer() const { // If the underlying LayoutObject has a layer it's guaranteed to be a // LayoutBoxModelObject. - return static_cast<LayoutBoxModelObject*>(layout_object_) - ->HasSelfPaintingLayer(); + return static_cast<LayoutBoxModelObject&>(layout_object_) + .HasSelfPaintingLayer(); } bool NGPhysicalFragment::HasOverflowClip() const { - return layout_object_ && layout_object_->HasOverflowClip(); + return !IsLineBox() && layout_object_.HasOverflowClip(); } bool NGPhysicalFragment::ShouldClipOverflow() const { - return layout_object_ && layout_object_->ShouldClipOverflow(); + return !IsLineBox() && layout_object_.ShouldClipOverflow(); } bool NGPhysicalFragment::IsBlockFlow() const { - return layout_object_ && layout_object_->IsLayoutBlockFlow(); + return !IsLineBox() && layout_object_.IsLayoutBlockFlow(); } bool NGPhysicalFragment::IsListMarker() const { - return layout_object_ && layout_object_->IsLayoutNGListMarker(); + return !IsLineBox() && layout_object_.IsLayoutNGListMarker(); } bool NGPhysicalFragment::IsPlacedByLayoutNG() const { // TODO(kojii): Move this to a flag for |LayoutNGBlockFlow::UpdateBlockLayout| // to set. - if (!layout_object_) + if (IsLineBox()) return false; - const LayoutBlock* container = layout_object_->ContainingBlock(); + const LayoutBlock* container = layout_object_.ContainingBlock(); if (!container) return false; return container->IsLayoutNGMixin() || container->IsLayoutNGFlexibleBox(); } -NGPixelSnappedPhysicalBoxStrut NGPhysicalFragment::BorderWidths() const { - unsigned edges = BorderEdges(); - NGPhysicalBoxStrut box_strut( - BorderWidth(edges, NGBorderEdges::kTop, Style().BorderTopWidth()), - BorderWidth(edges, NGBorderEdges::kRight, Style().BorderRightWidth()), - BorderWidth(edges, NGBorderEdges::kBottom, Style().BorderBottomWidth()), - BorderWidth(edges, NGBorderEdges::kLeft, Style().BorderLeftWidth())); - return box_strut.SnapToDevicePixels(); +const NGPhysicalFragment* NGPhysicalFragment::PostLayout() const { + if (IsBox() && !IsInlineBox()) { + if (const auto* block = DynamicTo<LayoutBlockFlow>(GetLayoutObject())) { + if (block->IsRelayoutBoundary()) { + const NGPhysicalFragment* new_fragment = block->CurrentFragment(); + if (new_fragment && new_fragment != this) + return new_fragment; + } + } + } + return nullptr; } #if DCHECK_IS_ON() @@ -365,7 +368,7 @@ void NGPhysicalFragment::CheckCanUpdateInkOverflow() const { } #endif -NGPhysicalOffsetRect NGPhysicalFragment::ScrollableOverflow() const { +PhysicalRect NGPhysicalFragment::ScrollableOverflow() const { switch (Type()) { case kFragmentBox: case kFragmentRenderedLegend: @@ -381,16 +384,17 @@ NGPhysicalOffsetRect NGPhysicalFragment::ScrollableOverflow() const { return {{}, Size()}; } -NGPhysicalOffsetRect NGPhysicalFragment::ScrollableOverflowForPropagation( +PhysicalRect NGPhysicalFragment::ScrollableOverflowForPropagation( const LayoutObject* container) const { DCHECK(container); - NGPhysicalOffsetRect overflow = ScrollableOverflow(); + PhysicalRect overflow = ScrollableOverflow(); if (GetLayoutObject() && GetLayoutObject()->ShouldUseTransformFromContainer(container)) { TransformationMatrix transform; - GetLayoutObject()->GetTransformFromContainer(container, LayoutSize(), + GetLayoutObject()->GetTransformFromContainer(container, PhysicalOffset(), transform); - overflow = NGPhysicalOffsetRect(transform.MapRect(overflow.ToLayoutRect())); + overflow = + PhysicalRect::EnclosingRect(transform.MapRect(FloatRect(overflow))); } return overflow; } @@ -447,6 +451,22 @@ TextDirection NGPhysicalFragment::ResolvedDirection() const { return TextDirection::kLtr; } +bool NGPhysicalFragment::ShouldPaintCursorCaret() const { + // TODO(xiaochengh): Merge cursor caret painting functions from LayoutBlock to + // FrameSelection. + if (const auto* block = DynamicTo<LayoutBlock>(GetLayoutObject())) + return block->ShouldPaintCursorCaret(); + return false; +} + +bool NGPhysicalFragment::ShouldPaintDragCaret() const { + // TODO(xiaochengh): Merge drag caret painting functions from LayoutBlock to + // DragCaret. + if (const auto* block = DynamicTo<LayoutBlock>(GetLayoutObject())) + return block->ShouldPaintDragCaret(); + return false; +} + String NGPhysicalFragment::ToString() const { StringBuilder output; output.AppendFormat("Type: '%d' Size: '%s'", Type(), @@ -473,7 +493,7 @@ String NGPhysicalFragment::ToString() const { String NGPhysicalFragment::DumpFragmentTree( DumpFlags flags, - base::Optional<NGPhysicalOffset> fragment_offset, + base::Optional<PhysicalOffset> fragment_offset, unsigned indent) const { StringBuilder string_builder; if (flags & DumpHeaderText) @@ -482,14 +502,14 @@ String NGPhysicalFragment::DumpFragmentTree( return string_builder.ToString(); } -#ifndef NDEBUG +#if DCHECK_IS_ON() void NGPhysicalFragment::ShowFragmentTree() const { DumpFlags dump_flags = DumpAll; LOG(INFO) << "\n" << DumpFragmentTree(dump_flags).Utf8().data(); } -#endif // !NDEBUG +#endif -NGPhysicalOffsetRect NGPhysicalFragmentWithOffset::RectInContainerBox() const { +PhysicalRect NGPhysicalFragmentWithOffset::RectInContainerBox() const { return {offset_to_container_box, fragment->Size()}; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h index d67ed752816..8ed0c068d70 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h @@ -8,10 +8,10 @@ #include "base/memory/scoped_refptr.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/editing/forward.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" -#include "third_party/blink/renderer/core/layout/ng/ng_break_token.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_size.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/ng/ng_style_variant.h" #include "third_party/blink/renderer/platform/graphics/touch_action.h" #include "third_party/blink/renderer/platform/wtf/ref_counted.h" @@ -21,12 +21,9 @@ namespace blink { class ComputedStyle; -class LayoutObject; class Node; class NGFragmentBuilder; -class NGBreakToken; class NGInlineItem; -struct NGPixelSnappedPhysicalBoxStrut; class PaintLayer; class NGPhysicalFragment; @@ -131,13 +128,13 @@ class CORE_EXPORT NGPhysicalFragment // inside the fieldset except the rendered legend). bool IsFieldsetContainer() const { return is_fieldset_container_; } - // Returns whether the fragment is old layout root. - bool IsOldLayoutRoot() const { return is_old_layout_root_; } + // Returns whether the fragment is legacy layout root. + bool IsLegacyLayoutRoot() const { return is_legacy_layout_root_; } bool IsBlockFormattingContextRoot() const { return (IsBox() && BoxType() >= NGBoxType::kMinimumBlockFormattingContextRoot) || - IsOldLayoutRoot(); + IsLegacyLayoutRoot(); } // |Offset()| is reliable only when this fragment was placed by LayoutNG @@ -151,24 +148,30 @@ class CORE_EXPORT NGPhysicalFragment // exist for paint, hit-testing, etc. // Returns the border-box size. - NGPhysicalSize Size() const { return size_; } + PhysicalSize Size() const { return size_; } // Returns the rect in the local coordinate of this fragment; i.e., offset is // (0, 0). - NGPhysicalOffsetRect LocalRect() const { return {{}, size_}; } + PhysicalRect LocalRect() const { return {{}, size_}; } - // Bitmask for border edges, see NGBorderEdges::Physical. - unsigned BorderEdges() const { return border_edge_; } - NGPixelSnappedPhysicalBoxStrut BorderWidths() const; - - NGBreakToken* BreakToken() const { return break_token_.get(); } NGStyleVariant StyleVariant() const { return static_cast<NGStyleVariant>(style_variant_); } bool UsesFirstLineStyle() const { return StyleVariant() == NGStyleVariant::kFirstLine; } - const ComputedStyle& Style() const; + + // Returns the style for this fragment. + // + // For a line box, this returns the style of the containing block. This mostly + // represents the style for the line box, except 1) |style.Direction()| maybe + // incorrect, use |BaseDirection()| instead, and 2) margin/border/padding, + // background etc. do not apply to the line box. + const ComputedStyle& Style() const { + return StyleVariant() == NGStyleVariant::kStandard + ? layout_object_.StyleRef() + : SlowEffectiveStyle(); + } Node* GetNode() const; // Whether there is a PaintLayer associated with the fragment. @@ -187,13 +190,32 @@ class CORE_EXPORT NGPhysicalFragment // GetLayoutObject should only be used when necessary for compatibility // with LegacyLayout. - LayoutObject* GetLayoutObject() const { return layout_object_; } + // + // For a line box, |layout_object_| has its containing block but this function + // returns |nullptr| for the historical reasons. TODO(kojii): We may change + // this in future. Use |IsLineBox()| instead of testing this is |nullptr|. + const LayoutObject* GetLayoutObject() const { + return !IsLineBox() ? &layout_object_ : nullptr; + } + // TODO(kojii): We should not have mutable version at all, the use of this + // function should be eliminiated over time. + LayoutObject* GetMutableLayoutObject() const { + return !IsLineBox() ? &layout_object_ : nullptr; + } + + // Returns the latest generation of the post-layout fragment. Returns + // |nullptr| if |this| is the one. + // + // When subtree relayout occurs at the relayout boundary, its containing block + // may keep the reference to old generations of this fragment. Callers can + // check if there were newer generations. + const NGPhysicalFragment* PostLayout() const; // Scrollable overflow. including contents, in the local coordinate. - NGPhysicalOffsetRect ScrollableOverflow() const; + PhysicalRect ScrollableOverflow() const; // ScrollableOverflow(), with transforms applied wrt container if needed. - NGPhysicalOffsetRect ScrollableOverflowForPropagation( + PhysicalRect ScrollableOverflowForPropagation( const LayoutObject* container) const; // The allowed touch action is the union of the effective touch action @@ -207,6 +229,14 @@ class CORE_EXPORT NGPhysicalFragment // be confused with the CSS 'direction' property. TextDirection ResolvedDirection() const; + // Utility functions for caret painting. Note that carets are painted as part + // of the containing block's foreground. + bool ShouldPaintCursorCaret() const; + bool ShouldPaintDragCaret() const; + bool ShouldPaintCarets() const { + return ShouldPaintCursorCaret() || ShouldPaintDragCaret(); + } + String ToString() const; void CheckCanUpdateInkOverflow() const; @@ -226,10 +256,10 @@ class CORE_EXPORT NGPhysicalFragment typedef int DumpFlags; String DumpFragmentTree(DumpFlags, - base::Optional<NGPhysicalOffset> = base::nullopt, + base::Optional<PhysicalOffset> = base::nullopt, unsigned indent = 2) const; -#ifndef NDEBUG +#if DCHECK_IS_ON() void ShowFragmentTree() const; #endif @@ -240,40 +270,51 @@ class CORE_EXPORT NGPhysicalFragment NGPhysicalFragment(LayoutObject* layout_object, NGStyleVariant, - NGPhysicalSize size, + PhysicalSize size, NGFragmentType type, - unsigned sub_type, - scoped_refptr<NGBreakToken> break_token = nullptr); + unsigned sub_type); + + const ComputedStyle& SlowEffectiveStyle() const; const Vector<NGInlineItem>& InlineItemsOfContainingBlock() const; - LayoutObject* const layout_object_; - const NGPhysicalSize size_; - scoped_refptr<NGBreakToken> break_token_; + LayoutObject& layout_object_; + const PhysicalSize size_; const unsigned type_ : 2; // NGFragmentType const unsigned sub_type_ : 3; // NGBoxType, NGTextType, or NGLineBoxType const unsigned style_variant_ : 2; // NGStyleVariant - // The following bitfield is only to be used by NGPhysicalContainerFragment + // The following bitfields are only to be used by NGPhysicalContainerFragment // (it's defined here to save memory, since that class has no bitfields). unsigned has_floating_descendants_ : 1; + unsigned has_orthogonal_flow_roots_ : 1; + unsigned may_have_descendant_above_block_start_ : 1; + unsigned depends_on_percentage_block_size_ : 1; - // The following bitfield is only to be used by NGPhysicalLineBoxFragment + // The following bitfields are only to be used by NGPhysicalLineBoxFragment // (it's defined here to save memory, since that class has no bitfields). + unsigned has_propagated_descendants_ : 1; unsigned base_direction_ : 1; // TextDirection + unsigned has_hanging_ : 1; - // The following bitfield is only to be used by NGPhysicalBoxFragment (it's - // defined here to save memory, since that class has no bitfields). + // The following bitfields are only to be used by NGPhysicalBoxFragment + // (it's defined here to save memory, since that class has no bitfields). unsigned children_inline_ : 1; - unsigned is_fieldset_container_ : 1; - unsigned is_old_layout_root_ : 1; unsigned border_edge_ : 4; // NGBorderEdges::Physical + unsigned has_borders_ : 1; + unsigned has_padding_ : 1; - // The following bitfield is only to be used by NGPhysicalTextFragment (it's - // defined here to save memory, since that class has no bitfields). + // The following are only used by NGPhysicalBoxFragment but are initialized + // for all types to allow methods using them to be inlined. + unsigned is_fieldset_container_ : 1; + unsigned is_legacy_layout_root_ : 1; + + // The following bitfields are only to be used by NGPhysicalTextFragment + // (it's defined here to save memory, since that class has no bitfields). unsigned line_orientation_ : 2; // NGLineOrientation - unsigned is_anonymous_text_ : 1; + unsigned is_generated_text_ : 1; + mutable unsigned ink_overflow_computed_ : 1; private: friend struct NGPhysicalFragmentTraits; @@ -285,9 +326,9 @@ struct CORE_EXPORT NGPhysicalFragmentWithOffset { DISALLOW_NEW(); scoped_refptr<const NGPhysicalFragment> fragment; - NGPhysicalOffset offset_to_container_box; + PhysicalOffset offset_to_container_box; - NGPhysicalOffsetRect RectInContainerBox() const; + PhysicalRect RectInContainerBox() const; }; #if !DCHECK_IS_ON() diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils.cc index a8ce3dbe009..e9837e027cd 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils.cc @@ -5,19 +5,19 @@ #include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h" #include "base/optional.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_size.h" #include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/platform/geometry/length_functions.h" namespace blink { // Returns the child's relative position wrt the containing fragment. -NGPhysicalOffset ComputeRelativeOffset(const ComputedStyle& child_style, - WritingMode container_writing_mode, - TextDirection container_direction, - NGPhysicalSize container_size) { - NGPhysicalOffset offset; +PhysicalOffset ComputeRelativeOffset(const ComputedStyle& child_style, + WritingMode container_writing_mode, + TextDirection container_direction, + PhysicalSize container_size) { + PhysicalOffset offset; if (child_style.GetPosition() != EPosition::kRelative) return offset; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils.h index 1291a8e2f87..8dc07e63815 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils.h @@ -6,22 +6,22 @@ #define NGRelativeUtils_h #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/platform/text/text_direction.h" namespace blink { class ComputedStyle; -struct NGPhysicalOffset; +struct PhysicalOffset; // Implements relative positioning spec: // https://www.w3.org/TR/css-position-3/#rel-pos // Return relative position offset as defined by style. -CORE_EXPORT NGPhysicalOffset +CORE_EXPORT PhysicalOffset ComputeRelativeOffset(const ComputedStyle& child_style, WritingMode container_writing_mode, TextDirection container_direction, - NGPhysicalSize container_size); + PhysicalSize container_size); } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils_test.cc index 216d0fa7744..763839f8f9b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_relative_utils_test.cc @@ -5,8 +5,8 @@ #include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset.h" -#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_size.h" #include "third_party/blink/renderer/core/style/computed_style.h" namespace blink { @@ -26,7 +26,7 @@ class NGRelativeUtilsTest : public testing::Test { void SetUp() override { style_ = ComputedStyle::Create(); style_->SetPosition(EPosition::kRelative); - container_size_ = NGPhysicalSize{kHorizontalSize, kVerticalSize}; + container_size_ = PhysicalSize{kHorizontalSize, kVerticalSize}; } void SetTRBL(LayoutUnit top, @@ -43,11 +43,11 @@ class NGRelativeUtilsTest : public testing::Test { } scoped_refptr<ComputedStyle> style_; - NGPhysicalSize container_size_; + PhysicalSize container_size_; }; TEST_F(NGRelativeUtilsTest, HorizontalTB) { - NGPhysicalOffset offset; + PhysicalOffset offset; // Everything auto defaults to kZero,kZero SetTRBL(kAuto, kAuto, kAuto, kAuto); @@ -80,7 +80,7 @@ TEST_F(NGRelativeUtilsTest, HorizontalTB) { } TEST_F(NGRelativeUtilsTest, VerticalRightLeft) { - NGPhysicalOffset offset; + PhysicalOffset offset; // Set all sides SetTRBL(kTop, kRight, kBottom, kLeft); @@ -106,7 +106,7 @@ TEST_F(NGRelativeUtilsTest, VerticalRightLeft) { } TEST_F(NGRelativeUtilsTest, VerticalLeftRight) { - NGPhysicalOffset offset; + PhysicalOffset offset; // Set all sides SetTRBL(kTop, kRight, kBottom, kLeft); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc new file mode 100644 index 00000000000..ed81dd98250 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc @@ -0,0 +1,213 @@ +// 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_simplified_layout_algorithm.h" + +#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.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.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" +#include "third_party/blink/renderer/core/layout/ng/ng_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h" + +namespace blink { + +NGSimplifiedLayoutAlgorithm::NGSimplifiedLayoutAlgorithm( + const NGLayoutAlgorithmParams& params, + const NGLayoutResult& result) + : NGLayoutAlgorithm(params), + previous_result_(result), + border_scrollbar_padding_(params.fragment_geometry.border + + params.fragment_geometry.scrollbar + + params.fragment_geometry.padding), + writing_mode_(Style().GetWritingMode()), + direction_(Style().Direction()), + exclusion_space_(ConstraintSpace().ExclusionSpace()) { + // Currently this only supports block-flow layout due to the static-position + // calculations. If support for other layout types is added this logic will + // need to be changed. + DCHECK(Node().IsBlockFlow()); + const NGPhysicalBoxFragment& physical_fragment = + To<NGPhysicalBoxFragment>(result.PhysicalFragment()); + + container_builder_.SetStyleVariant(physical_fragment.StyleVariant()); + container_builder_.SetIsNewFormattingContext( + physical_fragment.IsBlockFormattingContextRoot()); + container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); + + NGExclusionSpace exclusion_space = result.ExclusionSpace(); + container_builder_.SetExclusionSpace(std::move(exclusion_space)); + + // Ensure that the parent layout hasn't asked us to move our BFC position. + DCHECK_EQ(ConstraintSpace().BfcOffset(), + previous_result_.GetConstraintSpaceForCaching().BfcOffset()); + + container_builder_.SetBfcLineOffset(result.BfcLineOffset()); + if (result.BfcBlockOffset()) + container_builder_.SetBfcBlockOffset(*result.BfcBlockOffset()); + + container_builder_.SetEndMarginStrut(result.EndMarginStrut()); + container_builder_.SetIntrinsicBlockSize(result.IntrinsicBlockSize()); + container_builder_.SetUnpositionedListMarker(result.UnpositionedListMarker()); + + if (result.IsSelfCollapsing()) + container_builder_.SetIsSelfCollapsing(); + if (result.IsPushedByFloats()) + container_builder_.SetIsPushedByFloats(); + container_builder_.AddAdjoiningFloatTypes(result.AdjoiningFloatTypes()); + + for (const auto& request : ConstraintSpace().BaselineRequests()) { + base::Optional<LayoutUnit> baseline = physical_fragment.Baseline(request); + if (baseline) + container_builder_.AddBaseline(request, *baseline); + } + + container_builder_.SetBlockSize(ComputeBlockSizeForFragment( + ConstraintSpace(), Node(), + container_builder_.Borders() + container_builder_.Padding(), + result.IntrinsicBlockSize())); + + child_available_inline_size_ = + ShrinkAvailableSize(container_builder_.InitialBorderBoxSize(), + border_scrollbar_padding_) + .inline_size; + + // We need the previous physical container size to calculate the position of + // any child fragments. + previous_physical_container_size_ = physical_fragment.Size(); +} + +scoped_refptr<const NGLayoutResult> NGSimplifiedLayoutAlgorithm::Layout() { + const auto previous_child_fragments = + To<NGPhysicalBoxFragment>(previous_result_.PhysicalFragment()).Children(); + + const auto* it = previous_child_fragments.begin(); + const auto* end = previous_child_fragments.end(); + + // We may have a list-marker as our first child. This may have been + // propagated up to this container by an arbitrary child. As we don't know + // where it came from initially add it as the first child again. + if (it != end && (*it)->IsListMarker()) { + AddChildFragment(*it, *To<NGPhysicalContainerFragment>(it->get())); + ++it; + } + + // Initialize the static block-offset for any OOF-positioned children. + static_block_offset_ = border_scrollbar_padding_.block_start; + + for (NGLayoutInputNode child = Node().FirstChild(); child; + child = child.NextSibling()) { + // We've already dealt with any list-markers, so just skip this node. + if (child.IsListMarker()) + continue; + + if (child.IsOutOfFlowPositioned()) { + HandleOutOfFlowPositioned(To<NGBlockNode>(child)); + continue; + } + + DCHECK(it != end); + + if (child.IsInline()) { + // Simplified layout will only run if none of the lineboxes are dirty. + while (it != end && (*it)->IsLineBox()) { + // NOTE: When we remove continuations it'll be necessary for lineboxes + // to keep track of any exclusions they've added (and update the + // exclusion space). + AddChildFragment(*it, *To<NGPhysicalContainerFragment>(it->get())); + ++it; + } + continue; + } + + DCHECK_EQ((*it)->GetLayoutObject(), child.GetLayoutBox()); + + // Add the (potentially updated) layout result. + scoped_refptr<const NGLayoutResult> result = + To<NGBlockNode>(child).SimplifiedLayout(); + + // The child may have failed "simplified" layout! (Due to adding/removing + // scrollbars). In this case we also return a nullptr, indicating a full + // layout is required. + if (!result) + return nullptr; + + const NGPhysicalContainerFragment& fragment = result->PhysicalFragment(); + AddChildFragment(*it, fragment); + + const ComputedStyle& child_style = child.Style(); + + // Calculate the static block-offset for any OOF-positioned children. + NGMarginStrut margin_strut = result->EndMarginStrut(); + NGBoxStrut child_margins = ComputeMarginsFor( + child_style, child_available_inline_size_, writing_mode_, direction_); + margin_strut.Append(child_margins.block_end, + child_style.HasMarginBeforeQuirk()); + + static_block_offset_ += margin_strut.Sum(); + + // Only take exclusion spaces from children which don't establish their own + // formatting context. + if (!fragment.IsBlockFormattingContextRoot()) + exclusion_space_ = result->ExclusionSpace(); + ++it; + } + + NGOutOfFlowLayoutPart( + Node(), ConstraintSpace(), + container_builder_.Borders() + container_builder_.Scrollbar(), + &container_builder_) + .Run(); + + return container_builder_.ToBoxFragment(); +} + +void NGSimplifiedLayoutAlgorithm::HandleOutOfFlowPositioned( + const NGBlockNode& child) { + LogicalOffset static_offset = {border_scrollbar_padding_.inline_start, + static_block_offset_}; + + if (child.Style().IsOriginalDisplayInlineType()) { + NGBfcOffset origin_bfc_offset = { + container_builder_.BfcLineOffset() + + border_scrollbar_padding_.LineLeft(direction_), + (container_builder_.BfcBlockOffset() + ? *container_builder_.BfcBlockOffset() + : ConstraintSpace().BfcOffset().block_offset) + + static_block_offset_}; + + static_offset.inline_offset += CalculateOutOfFlowStaticInlineLevelOffset( + Style(), origin_bfc_offset, exclusion_space_, + child_available_inline_size_); + } + + container_builder_.AddOutOfFlowChildCandidate(child, static_offset); +} + +void NGSimplifiedLayoutAlgorithm::AddChildFragment( + const NGLink& old_fragment, + const NGPhysicalContainerFragment& new_fragment) { + DCHECK_EQ(old_fragment->Size(), new_fragment.Size()); + + PhysicalSize physical_child_size = new_fragment.Size(); + LogicalSize child_size = physical_child_size.ConvertToLogical(writing_mode_); + + // Determine the previous position in the logical coordinate system. + LogicalOffset child_offset = old_fragment.Offset().ConvertToLogical( + writing_mode_, direction_, previous_physical_container_size_, + physical_child_size); + + // Add the new fragemnt to the builder. + container_builder_.AddChild(new_fragment, child_offset); + + // Update the static block-offset for any OOF-positioned children. + static_block_offset_ = child_offset.block_offset + child_size.block_size; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.h new file mode 100644 index 00000000000..29dc6351982 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.h @@ -0,0 +1,68 @@ +// 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_SIMPLIFIED_LAYOUT_ALGORITHM_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_SIMPLIFIED_LAYOUT_ALGORITHM_H_ + +#include "third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h" + +#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" + +namespace blink { + +class NGBlockBreakToken; +struct NGLink; +class NGPhysicalContainerFragment; + +// The "simplified" layout algorithm will run in the following circumstances: +// - An OOF-positioned descendant of this node (this node is its containing +// block) has its constraints changed. +// - A child requires "simplified" layout, i.e. an indirect-descendant +// OOF-positioned child has its constraints changed. +// - The block-size of the fragment has changed, and we know that it won't +// affect any inflow children (no %-block-size descendants). +// +// This algorithm effectively performs a (convoluted) "copy" of the previous +// layout result. It will: +// 1. Copy data from the previous |NGLayoutResult| into the +// |NGBoxFragmentBuilder|, (e.g. flags, end margin strut, etc). +// 2. Iterate through all the children and: +// a. If OOF-positioned determine the static-position and add it as an +// OOF-positioned candidate. +// b. Otherwise perform layout on the inflow child (which may trigger +// "simplified" layout on its children). +// 3. Run the |NGOutOfFlowLayoutPart|. +class CORE_EXPORT NGSimplifiedLayoutAlgorithm + : public NGLayoutAlgorithm<NGBlockNode, + NGBoxFragmentBuilder, + NGBlockBreakToken> { + public: + NGSimplifiedLayoutAlgorithm(const NGLayoutAlgorithmParams&, + const NGLayoutResult&); + + scoped_refptr<const NGLayoutResult> Layout() override; + + private: + void HandleOutOfFlowPositioned(const NGBlockNode&); + + void AddChildFragment(const NGLink& old_fragment, + const NGPhysicalContainerFragment& new_fragment); + + const NGLayoutResult& previous_result_; + const NGBoxStrut border_scrollbar_padding_; + + const WritingMode writing_mode_; + const TextDirection direction_; + + LayoutUnit child_available_inline_size_; + PhysicalSize previous_physical_container_size_; + + LayoutUnit static_block_offset_; + NGExclusionSpace exclusion_space_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_SIMPLIFIED_LAYOUT_ALGORITHM_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc index 8cb6b8f4d29..ba44dfdb772 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc @@ -30,7 +30,7 @@ NGConstraintSpace CreateIndefiniteConstraintSpaceForChild( WritingMode child_writing_mode = child.Style().GetWritingMode(); DCHECK(!IsParallelWritingMode(parent_writing_mode, child_writing_mode)); - NGLogicalSize indefinite_size(NGSizeIndefinite, NGSizeIndefinite); + LogicalSize indefinite_size(kIndefiniteSize, kIndefiniteSize); NGConstraintSpaceBuilder builder(parent_writing_mode, child_writing_mode, child.CreatesNewFormattingContext()); SetOrthogonalFallbackInlineSizeIfNeeded(container_style, child, &builder); @@ -39,7 +39,6 @@ NGConstraintSpace CreateIndefiniteConstraintSpaceForChild( .SetPercentageResolutionSize(indefinite_size) .SetReplacedPercentageResolutionSize(indefinite_size) .SetIsIntermediateLayout(true) - .SetFloatsBfcBlockOffset(LayoutUnit()) .ToConstraintSpace(); } @@ -51,7 +50,7 @@ void SetOrthogonalFallbackInlineSizeIfNeeded( child.Style().GetWritingMode())) return; - NGPhysicalSize orthogonal_children_containing_block_size = + PhysicalSize orthogonal_children_containing_block_size = child.InitialContainingBlockSize(); LayoutUnit fallback_size; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc index 9d7badb5119..559bef22a49 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc @@ -20,7 +20,6 @@ int NGTextDecorationOffset::ComputeUnderlineOffsetForUnder( FontBaseline baseline_type = style.GetFontBaseline(); if (decorating_box_) { - // TODO(eae): Replace with actual baseline once available. NGBaselineRequest baseline_request = { NGBaselineAlgorithmType::kAtomicInline, FontBaseline::kIdeographicBaseline}; |